diff --git a/.gitignore b/.gitignore index 7ca4f00c7..be3b22e67 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,9 @@ packages/audio_streamer/example/.flutter-plugins-dependencies *.sh packages/health/example/.flutter-plugins-dependencies *.sh +packages/health/example/.flutter-plugins-dependencies +packages/health/example/.flutter-plugins-dependencies +packages/activity_recognition_flutter/example/.flutter-plugins-dependencies +packages/mobility_features/example/.flutter-plugins-dependencies +packages/mobility_features/example/.flutter-plugins-dependencies +packages/health/example/.flutter-plugins-dependencies diff --git a/README.md b/README.md index b54df5bf2..ddedf068b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ For more information about plugins, and how to use them, see ## Plugins These are the available plugins in this repository. -| Plugin | Description | Android | iOS | Pub | +| Plugin | Description | Android | iOS | http://pub.dev/ | |--------|-------------|:-------:|:---:|:---------:| | [screen_state](./packages/screen_state) | Track screen state changes | ✔️ | ❌ | [![pub package](https://img.shields.io/pub/v/screen_state.svg)](https://pub.dartlang.org/packages/screen_state) | | [light](./packages/light) | Track light sensor readings | ✔️ | ❌ | [![pub package](https://img.shields.io/pub/v/light.svg)](https://pub.dartlang.org/packages/light) | @@ -35,7 +35,15 @@ Please check existing issues and file any new issues, bugs, or feature requests ## Contributing -If you wish to contribute a new plugin to the Flutter ecosystem, please +As part of the open-source Flutter ecosystem, we would welcome any help in maintaining and enhancing these plugins. +We (i.e., CACHET) have limited resources for maintaining these plugins and we rely on **your** help in this. +We welcome any contribution -- from small error corrections in the documentation, to bug fixes, to large features enhacements, or even new features in a plugin. +If you wish to contribute to any of the plugins in this repo, +please review our [contribution guide](https://github.com/cph-cachet/flutter-plugins/CONTRIBUTING.md), +and send a [pull request](https://github.com/cph-cachet/flutter-plugins/pulls). + + +In general, if you wish to contribute a new plugin to the Flutter ecosystem, please see the documentation for [developing packages](https://flutter.io/developing-packages/) and [platform channels](https://flutter.io/platform-channels/). You can store your plugin source code in any GitHub repository (the present repo is only @@ -43,7 +51,3 @@ intended for plugins developed by the core CARP team). Once your plugin is ready you can [publish](https://flutter.io/developing-packages/#publish) to the [pub repository](https://pub.dartlang.org/). -If you wish to contribute a change to any of the existing plugins in this repo, -please review our [contribution guide](https://github.com/cph-cachet/flutter-plugins/CONTRIBUTING.md), -and send a [pull request](https://github.com/cph-cachet/flutter-plugins/pulls). - diff --git a/packages/activity_recognition_flutter/CHANGELOG.md b/packages/activity_recognition_flutter/CHANGELOG.md index 37e1eef49..df13d9fb4 100644 --- a/packages/activity_recognition_flutter/CHANGELOG.md +++ b/packages/activity_recognition_flutter/CHANGELOG.md @@ -1,3 +1,12 @@ +## 4.2.0 +* small refactor and improvement of docs +* using `ActivityRecognition()` when creating a singleton -- standard practice in Dart. + +## 4.1.0 +* [PR #474](https://github.com/cph-cachet/flutter-plugins/pull/474) - Android 12 intent-flag +* the name of the stream has been changed from `startStream` to `activityStream` +* cleanup in example app + ## 4.0.5+1 * [PR #408](https://github.com/cph-cachet/flutter-plugins/pull/408) @@ -17,7 +26,6 @@ * [PR #314](https://github.com/cph-cachet/flutter-plugins/pull/314) ## 4.0.0 - - Null safety migration - Updated swift code diff --git a/packages/activity_recognition_flutter/android/build.gradle b/packages/activity_recognition_flutter/android/build.gradle index 30a2256d5..0a1316904 100644 --- a/packages/activity_recognition_flutter/android/build.gradle +++ b/packages/activity_recognition_flutter/android/build.gradle @@ -22,10 +22,10 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { - compileSdkVersion 29 + compileSdkVersion 30 defaultConfig { - minSdkVersion 16 + minSdkVersion 21 } lintOptions { disable 'InvalidPackage' @@ -33,5 +33,5 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-location:16.0.0' + implementation 'com.google.android.gms:play-services-location:19.0.1' } diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java index 67d70be7f..6df5f1cd3 100644 --- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java +++ b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java @@ -45,7 +45,13 @@ public class ActivityRecognitionFlutterPlugin implements FlutterPlugin, EventCha private void startActivityTracking() { // Start the service Intent intent = new Intent(androidActivity, ActivityRecognizedBroadcastReceiver.class); - PendingIntent pendingIntent = PendingIntent.getBroadcast(androidActivity, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + Log.d(TAG, "SDK = " + Build.VERSION.SDK_INT); + int flags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= 31) { + flags |= PendingIntent.FLAG_IMMUTABLE; + } + PendingIntent pendingIntent = PendingIntent.getBroadcast(androidActivity, 0, intent, flags); // Frequency in milliseconds long frequency = 5 * 1000; @@ -55,13 +61,13 @@ private void startActivityTracking() { task.addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void e) { - Log.d(TAG, "ActivityRecognition: onSuccess"); + Log.d(TAG, "Successfully registered ActivityRecognition listener."); } }); task.addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { - Log.d(TAG, "ActivityRecognition: onFailure"); + Log.d(TAG, "Failed to registered ActivityRecognition listener."); } }); } @@ -82,13 +88,11 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin @Override public void onListen(Object arguments, EventChannel.EventSink events) { HashMap args = (HashMap) arguments; - Log.d(TAG, "args: " + args); boolean fg = (boolean) args.get("foreground"); if(fg) { startForegroundService(); } - Log.d(TAG, "foreground: " + fg); - + Log.d(TAG, "Foreground mode: " + fg); eventSink = events; startActivityTracking(); @@ -132,7 +136,7 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { SharedPreferences prefs = androidContext.getSharedPreferences(ACTIVITY_RECOGNITION, Context.MODE_PRIVATE); prefs.registerOnSharedPreferenceChangeListener(this); - Log.d(TAG, "onAttachedToActivity"); + // Log.d(TAG, "onAttachedToActivity"); } @Override @@ -161,9 +165,9 @@ public void onDetachedFromActivity() { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { String result = sharedPreferences .getString(DETECTED_ACTIVITY, "error"); - Log.d("onSharedPreferenceChange", result); + // Log.d("onSharedPreferenceChange", result); if (key!= null && key.equals(DETECTED_ACTIVITY)) { - Log.d(TAG, "Detected activity: " + result); + // Log.d(TAG, "Detected activity: " + result); eventSink.success(result); } } diff --git a/packages/activity_recognition_flutter/example/.flutter-plugins-dependencies b/packages/activity_recognition_flutter/example/.flutter-plugins-dependencies deleted file mode 100644 index f70808a88..000000000 --- a/packages/activity_recognition_flutter/example/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"activity_recognition_flutter","path":"/Users/bardram/dev/flutter-plugins/packages/activity_recognition_flutter/","dependencies":[]},{"name":"permission_handler","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/permission_handler-7.1.0/","dependencies":[]}],"android":[{"name":"activity_recognition_flutter","path":"/Users/bardram/dev/flutter-plugins/packages/activity_recognition_flutter/","dependencies":[]},{"name":"permission_handler","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/permission_handler-7.1.0/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"activity_recognition_flutter","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2021-10-06 09:54:26.345214","version":"2.2.3"} \ No newline at end of file diff --git a/packages/activity_recognition_flutter/example/android/app/build.gradle b/packages/activity_recognition_flutter/example/android/app/build.gradle index 94c69ba23..67e070f7f 100644 --- a/packages/activity_recognition_flutter/example/android/app/build.gradle +++ b/packages/activity_recognition_flutter/example/android/app/build.gradle @@ -34,7 +34,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "dk.cachet.activity_recognition_flutter_example" - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh b/packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh deleted file mode 100755 index 934c5797a..000000000 --- a/packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/bardram/dev/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/bardram/dev/flutter-plugins/packages/activity_recognition_flutter/example" -export "COCOAPODS_PARALLEL_CODE_SIGN=true" -export "FLUTTER_TARGET=lib/main.dart" -export "FLUTTER_BUILD_DIR=build" -export "SYMROOT=${SOURCE_ROOT}/../build/ios" -export "FLUTTER_BUILD_NAME=1.0.0" -export "FLUTTER_BUILD_NUMBER=1" -export "DART_OBFUSCATION=false" -export "TRACK_WIDGET_CREATION=false" -export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=.packages" diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj index 12033be9e..459ed6684 100644 --- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -321,6 +321,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -339,7 +340,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -400,6 +401,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -424,7 +426,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -455,6 +457,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -473,7 +476,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -491,7 +494,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = H33599VJ27; + DEVELOPMENT_TEAM = 8TB3T6MAZG; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 6b30c7459..530b83358 100644 --- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -3,7 +3,7 @@ BuildSystemType - Original + Latest PreviewsEnabled diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..6dd6010f8 100644 --- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad diff --git a/packages/activity_recognition_flutter/example/lib/main.dart b/packages/activity_recognition_flutter/example/lib/main.dart index 9050184c3..32680bda6 100644 --- a/packages/activity_recognition_flutter/example/lib/main.dart +++ b/packages/activity_recognition_flutter/example/lib/main.dart @@ -1,75 +1,114 @@ +import 'dart:async'; import 'dart:io'; import 'package:activity_recognition_flutter/activity_recognition_flutter.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; -void main() => runApp(new MyApp()); +void main() => runApp(new ActivityRecognitionApp()); -class MyApp extends StatefulWidget { +class ActivityRecognitionApp extends StatefulWidget { @override - _MyAppState createState() => new _MyAppState(); + _ActivityRecognitionAppState createState() => _ActivityRecognitionAppState(); } -class _MyAppState extends State { - late Stream activityStream; - ActivityEvent latestActivity = ActivityEvent.empty(); +class _ActivityRecognitionAppState extends State { + StreamSubscription? activityStreamSubscription; List _events = []; - ActivityRecognition activityRecognition = ActivityRecognition.instance; + ActivityRecognition activityRecognition = ActivityRecognition(); @override void initState() { super.initState(); _init(); + _events.add(ActivityEvent.unknown()); + } + + @override + void dispose() { + activityStreamSubscription?.cancel(); + super.dispose(); } void _init() async { - /// Android requires explicitly asking permission + // Android requires explicitly asking permission if (Platform.isAndroid) { if (await Permission.activityRecognition.request().isGranted) { _startTracking(); } } - /// iOS does not + // iOS does not else { _startTracking(); } } void _startTracking() { - activityStream = - activityRecognition.startStream(runForegroundService: true); - activityStream.listen(onData); + activityStreamSubscription = activityRecognition + .activityStream(runForegroundService: true) + .listen(onData, onError: onError); } void onData(ActivityEvent activityEvent) { - print(activityEvent.toString()); + print(activityEvent); setState(() { _events.add(activityEvent); - latestActivity = activityEvent; }); } + void onError(Object error) { + print('ERROR - $error'); + } + @override Widget build(BuildContext context) { - return new MaterialApp( - home: new Scaffold( - appBar: new AppBar( - title: const Text('Activity Recognition Demo'), + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Activity Recognition'), + ), + body: Center( + child: ListView.builder( + itemCount: _events.length, + reverse: true, + itemBuilder: (_, int idx) { + final activity = _events[idx]; + return ListTile( + leading: _activityIcon(activity.type), + title: Text( + '${activity.type.toString().split('.').last} (${activity.confidence}%)'), + trailing: Text(activity.timeStamp + .toString() + .split(' ') + .last + .split('.') + .first), + ); + }), ), - body: new Center( - child: new ListView.builder( - itemCount: _events.length, - reverse: true, - itemBuilder: (BuildContext context, int idx) { - final entry = _events[idx]; - return ListTile( - leading: - Text(entry.timeStamp.toString().substring(0, 19)), - trailing: Text(entry.type.toString().split('.').last)); - })), ), ); } + + Icon _activityIcon(ActivityType type) { + switch (type) { + case ActivityType.WALKING: + return Icon(Icons.directions_walk); + case ActivityType.IN_VEHICLE: + return Icon(Icons.car_rental); + case ActivityType.ON_BICYCLE: + return Icon(Icons.pedal_bike); + case ActivityType.ON_FOOT: + return Icon(Icons.directions_walk); + case ActivityType.RUNNING: + return Icon(Icons.run_circle); + case ActivityType.STILL: + return Icon(Icons.cancel_outlined); + case ActivityType.TILTING: + return Icon(Icons.redo); + default: + return Icon(Icons.device_unknown); + } + } } diff --git a/packages/activity_recognition_flutter/example/pubspec.yaml b/packages/activity_recognition_flutter/example/pubspec.yaml index 46219a48b..479b39522 100644 --- a/packages/activity_recognition_flutter/example/pubspec.yaml +++ b/packages/activity_recognition_flutter/example/pubspec.yaml @@ -1,4 +1,5 @@ name: activity_recognition_flutter_example +version: 4.1.0 description: Demonstrates how to use the activity_recognition_flutter plugin. # The following line prevents the package from being accidentally published to diff --git a/packages/activity_recognition_flutter/example/test/widget_test.dart b/packages/activity_recognition_flutter/example/test/widget_test.dart index 90c68b250..6e03e9854 100644 --- a/packages/activity_recognition_flutter/example/test/widget_test.dart +++ b/packages/activity_recognition_flutter/example/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:activity_recognition_flutter_example/main.dart'; void main() { testWidgets('Verify Platform version', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(ActivityRecognitionApp()); // Verify that platform version is retrieved. expect( diff --git a/packages/activity_recognition_flutter/lib/ar_domain.dart b/packages/activity_recognition_flutter/lib/activity_recognition_domain.dart similarity index 66% rename from packages/activity_recognition_flutter/lib/ar_domain.dart rename to packages/activity_recognition_flutter/lib/activity_recognition_domain.dart index 02d4216e9..db65d7620 100644 --- a/packages/activity_recognition_flutter/lib/ar_domain.dart +++ b/packages/activity_recognition_flutter/lib/activity_recognition_domain.dart @@ -37,21 +37,28 @@ Map _activityMap = { /// Represents an activity event detected on the phone. class ActivityEvent { - ActivityType _type; - int _confidence; - late DateTime _timeStamp; + /// The type of activity. + ActivityType type; + + /// The confidence of the dection in percentage. + int confidence; - ActivityEvent._(this._type, this._confidence) { - this._timeStamp = DateTime.now(); + /// The timestamp when detected. + late DateTime timeStamp; + + /// The type of activity as a String. + String get typeString => type.toString().split('.').last; + + ActivityEvent(this.type, this.confidence) { + this.timeStamp = DateTime.now(); } - factory ActivityEvent.empty() => ActivityEvent._(ActivityType.UNKNOWN, 100); + factory ActivityEvent.unknown() => ActivityEvent(ActivityType.UNKNOWN, 100); - factory ActivityEvent.fromJson(String json) { - List tokens = json.split(","); - if (tokens.length < 2) { - return ActivityEvent.empty(); - } + /// Create an [ActivityEvent] based on the string format `type,confidence`. + factory ActivityEvent.fromString(String string) { + List tokens = string.split(","); + if (tokens.length < 2) return ActivityEvent.unknown(); ActivityType type = ActivityType.UNKNOWN; if (_activityMap.containsKey(tokens.first)) { @@ -59,21 +66,9 @@ class ActivityEvent { } int conf = int.tryParse(tokens.last)!; - return ActivityEvent._(type, conf); + return ActivityEvent(type, conf); } @override - String toString() { - String typeString = type.toString().split('.').last; - return 'Activity: $typeString, confidence: $confidence%'; - } - - /// The type of activity. - ActivityType get type => _type; - - /// The timestamp when detected. - DateTime get timeStamp => _timeStamp; - - /// The confidence of the dection in percentage. - int get confidence => _confidence; + String toString() => 'Activity - type: $typeString, confidence: $confidence%'; } diff --git a/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart b/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart index 2b7f86f69..4158d59c9 100644 --- a/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart +++ b/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart @@ -3,23 +3,21 @@ library activity_recognition; import 'dart:async'; import 'package:flutter/services.dart'; -part 'ar_domain.dart'; +part 'activity_recognition_domain.dart'; /// Main entry to activity recognition API. Use as a singleton like /// -/// `ActivityRecognition.instance` +/// `ActivityRecognition()` /// class ActivityRecognition { + static const EventChannel _eventChannel = + const EventChannel('activity_recognition_flutter'); Stream? _stream; - + static ActivityRecognition _instance = ActivityRecognition._(); ActivityRecognition._(); - static final ActivityRecognition _instance = ActivityRecognition._(); - - static ActivityRecognition get instance => _instance; - - static const EventChannel _eventChannel = - const EventChannel('activity_recognition_flutter'); + /// Get the [ActivityRecognition] singleton. + factory ActivityRecognition() => _instance; /// Requests continuous [ActivityEvent] updates. /// @@ -27,11 +25,11 @@ class ActivityRecognition { /// By default the foreground service is enabled, which allows the /// updates to be streamed while the app runs in the background. /// The programmer can choose to not enable to foreground service. - Stream startStream({bool runForegroundService = true}) { + Stream activityStream({bool runForegroundService = true}) { if (_stream == null) { _stream = _eventChannel .receiveBroadcastStream({"foreground": runForegroundService}).map( - (json) => ActivityEvent.fromJson(json)); + (json) => ActivityEvent.fromString(json)); } return _stream!; } diff --git a/packages/activity_recognition_flutter/pubspec.yaml b/packages/activity_recognition_flutter/pubspec.yaml index e10533abb..196524ac0 100644 --- a/packages/activity_recognition_flutter/pubspec.yaml +++ b/packages/activity_recognition_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: activity_recognition_flutter description: Activity recognition plugin for Android and iOS. Provides event-based information about activities detected by the phone. -version: 4.0.5+1 +version: 4.2.0 homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages environment: diff --git a/packages/carp_background_location/example/android/app/build.gradle b/packages/carp_background_location/example/android/app/build.gradle index 320be2219..dcb69350b 100644 --- a/packages/carp_background_location/example/android/app/build.gradle +++ b/packages/carp_background_location/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,7 +40,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" minSdkVersion 16 - targetSdkVersion 28 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/packages/carp_background_location/example/android/app/src/main/AndroidManifest.xml b/packages/carp_background_location/example/android/app/src/main/AndroidManifest.xml index 4ee714d9b..4a8a4927e 100644 --- a/packages/carp_background_location/example/android/app/src/main/AndroidManifest.xml +++ b/packages/carp_background_location/example/android/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ - + + + + diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md index dd43a2865..eb5af0bbe 100644 --- a/packages/health/CHANGELOG.md +++ b/packages/health/CHANGELOG.md @@ -1,26 +1,42 @@ +## 3.4.3 +* fix of [#401](https://github.com/cph-cachet/flutter-plugins/issues/401). + +## 3.4.2 +* Resolved concurrent issues with native threads [PR#483](https://github.com/cph-cachet/flutter-plugins/pull/483). +* Healthkit CategorySample [PR#485](https://github.com/cph-cachet/flutter-plugins/pull/485). +* update of API documentation. + +## 3.4.0 +* Add sleep in bed to android [PR#457](https://github.com/cph-cachet/flutter-plugins/pull/457). +* Add the android.permission.ACTIVITY_RECOGNITION setup to the README [PR#458](https://github.com/cph-cachet/flutter-plugins/pull/458). +* Fixed (regression) issues with metric and permissions [PR#462](https://github.com/cph-cachet/flutter-plugins/pull/462). +* Get total steps [PR#471](https://github.com/cph-cachet/flutter-plugins/pull/471). +* update of example app to refelct new features. +* update of API documentation. + ## 3.3.1 -* [PR #428](https://github.com/cph-cachet/flutter-plugins/pull/428) - DISTANCE_DELTA is for Android, not iOS -* [PR #454](https://github.com/cph-cachet/flutter-plugins/pull/454) - added missing READ_ACCESS +* DISTANCE_DELTA is for Android, not iOS [PR#428](https://github.com/cph-cachet/flutter-plugins/pull/428). +* added missing READ_ACCESS [PR#454](https://github.com/cph-cachet/flutter-plugins/pull/454). ## 3.3.0 -* [PR #430](https://github.com/cph-cachet/flutter-plugins/pull/430) - Write support on Google Fit and HealthKit +* Write support on Google Fit and HealthKit [PR#430](https://github.com/cph-cachet/flutter-plugins/pull/430). ## 3.2.1 * Updated `device_info_plus` version dependency ## 3.2.0 -* [PR #421](https://github.com/cph-cachet/flutter-plugins/pull/421) - added simple `HKWorkout` and `ExerciseTime` support +* added simple `HKWorkout` and `ExerciseTime` support [PR#421](https://github.com/cph-cachet/flutter-plugins/pull/421). ## 3.1.1+1 -* [PR #394](https://github.com/cph-cachet/flutter-plugins/pull/394) - added functions to request authorization +* added functions to request authorization [PR#394](https://github.com/cph-cachet/flutter-plugins/pull/394) ## 3.1.0 -* [PR #372](https://github.com/cph-cachet/flutter-plugins/pull/372) - added sleep data to Android + fix of permissions and initialization -* [PR #388](https://github.com/cph-cachet/flutter-plugins/pull/388) - testability of HealthDataPoint +* added sleep data to Android + fix of permissions and initialization [PR#372](https://github.com/cph-cachet/flutter-plugins/pull/372) +* testability of HealthDataPoint [PR#388](https://github.com/cph-cachet/flutter-plugins/pull/388). * update to using the `device_info_plus` plugin ## 3.0.6 -* [PR#281](https://github.com/cph-cachet/flutter-plugins/pull/281) Added two new fields to the `HealthDataPoint` - `SourceId` and `SourceName` and populate when data is read. This allows datapoints to be disambigous and in some cases allows us to get more accurate data. For example the number of steps can be reported from Apple Health and Watch and without source data they are aggregated into just "steps" producing an innacurate result. +* Added two new fields to the `HealthDataPoint` - `SourceId` and `SourceName` and populate when data is read. This allows datapoints to be disambigous and in some cases allows us to get more accurate data. For example the number of steps can be reported from Apple Health and Watch and without source data they are aggregated into just "steps" producing an innacurate result [PR#281](https://github.com/cph-cachet/flutter-plugins/pull/281). ## 3.0.5 * Null safety in Dart has been implemented diff --git a/packages/health/README.md b/packages/health/README.md index 32692b9bc..e42508779 100644 --- a/packages/health/README.md +++ b/packages/health/README.md @@ -1,14 +1,22 @@ # Health -This library combines both GoogleFit and AppleHealthKit. It support most of the values provided. +Enables reading and writing health data from/to Apple Health and Google Fit. -Supports **iOS** and **Android X** +The plugin supports: -NB: For Android, your app _needs_ to have Google Fit installed and have access to the internet, otherwise this plugin will not work. + * handling permissions to access health data using the `hasPermissions`, + `requestPermissions`, `requestAuthorization`, `revokePermissions` methods. + * reading health data using the `getHealthDataFromTypes` method. + * writing health data using the `writeHealthData` method. + * accessing total step counts using the `getTotalStepsInInterval` method. + * cleaning up dublicate data points via the `removeDuplicates` method. + + +Note that for Android, the target phone __needs__ to have [Google Fit](https://www.google.com/fit/) installed and have access to the internet, otherwise this plugin will not work. ## Data Types -| **Data Type** | **Unit** | **iOS** | **Android** | **Comments** | +| **Data Type** | **Unit** | **iOS** | **Android** | **Comments** | | --------------------------- | ----------------------- | ----------- | ------------ | ----------------------------------------------------------- | | ACTIVE_ENERGY_BURNED | CALORIES | yes | yes | | | BASAL_ENERGY_BURNED | CALORIES | yes | | | @@ -46,7 +54,7 @@ NB: For Android, your app _needs_ to have Google Fit installed and have access t ## Setup -### Apple HealthKit (iOS) +### Apple Health (iOS) Step 1: Append the Info.plist with the following 2 entries @@ -92,7 +100,28 @@ Certificate fingerprints: Follow the instructions at https://console.developers.google.com/flows/enableapi?apiid=fitness for setting up an OAuth2 Client ID for a Google project, and adding the SHA1 fingerprint to that OAuth2 credential. -The client id will look something like `YOUR_CLIENT_ID.apps.googleusercontent.com` +The client id will look something like `YOUR_CLIENT_ID.apps.googleusercontent.com`. + +### Android Permissions + +Starting from API level 28 (Android 9.0) acessing some fitness data (e.g. Steps) requires a special permission. + +To set it add the following line to your `AndroidManifest.xml` file. + +``` + +``` + +There's a `debug`, `main` and `profile` version which are chosen depending on how you start your app. In general, it's sufficient to add permission only to the `main` version. + +Beacuse this is labled as a `dangerous` protection level, the permission system will not grant it automaticlly and it requires the user's action. +You can prompt the user for it using the [permission_handler](https://pub.dev/packages/permission_handler) plugin. +Follow the plugin setup instructions and add the following line before requsting the data: + + ``` + await Permission.activityRecognition.request(); + ``` + ### Android X @@ -106,9 +135,50 @@ android.useAndroidX=true ## Usage -Below is a snippet from the `example app` showing the plugin in use. +See the example app for detailed examples of how to use the Health API. + +The Health plugin is used via the `HealthFactory` class using the different methods for handling permissions and getting and adding data to Apple Health / Google Fit. +Below is a simplified flow of how to use the plugin. + +```dart + // create a HealthFactory for use in the app + HealthFactory health = HealthFactory(); + + // define the types to get + var types = [ + HealthDataType.STEPS, + HealthDataType.WEIGHT, + HealthDataType.HEIGHT, + HealthDataType.BLOOD_GLUCOSE, + ]; + + // requesting access to the data types before reading them + bool requested = await health.requestAuthorization(types); + + var now = DateTime.now(); + + // fetch health data from the last 24 hours + List healthData = await health.getHealthDataFromTypes( + now.subtract(Duration(days: 1)), now, types); + + // request permissions to write steps and blood glucose + types = [HealthDataType.STEPS, HealthDataType.BLOOD_GLUCOSE]; + var permissions = [ + HealthDataAccess.READ_WRITE, + HealthDataAccess.READ_WRITE + ]; + await health.requestAuthorization(types, permissions: permissions); + + // write steps and blood glucose + bool success = await health.writeHealthData(10, HealthDataType.STEPS, now, now); + success = await health.writeHealthData(3.1, HealthDataType.BLOOD_GLUCOSE, now, now); + + // get the number of steps for today + var midnight = DateTime(now.year, now.month, now.day); + int? steps = await health.getTotalStepsInInterval(midnight, now); +``` -### Health data +### Health Data A `HealthData` object contains the following data fields: diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index 5f7d03459..0ac228f9a 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -6,7 +6,6 @@ import com.google.android.gms.fitness.Fitness import com.google.android.gms.fitness.FitnessOptions import com.google.android.gms.fitness.request.DataReadRequest import com.google.android.gms.fitness.result.DataReadResponse -import com.google.android.gms.tasks.Tasks import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler @@ -17,13 +16,18 @@ import android.os.Handler import android.util.Log import androidx.annotation.NonNull import io.flutter.plugin.common.PluginRegistry.ActivityResultListener -import java.util.concurrent.TimeUnit -import kotlin.concurrent.thread import com.google.android.gms.fitness.data.* import com.google.android.gms.fitness.request.SessionReadRequest +import com.google.android.gms.fitness.result.SessionReadResponse +import com.google.android.gms.tasks.OnFailureListener +import com.google.android.gms.tasks.OnSuccessListener import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import java.util.concurrent.TimeUnit +import java.util.Date + +import java.util.concurrent.* const val GOOGLE_FIT_PERMISSIONS_REQUEST_CODE = 1111 @@ -34,11 +38,13 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl private var result: Result? = null private var handler: Handler? = null private var activity: Activity? = null + private var threadPoolExecutor: ExecutorService? = null private var BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE" private var HEIGHT = "HEIGHT" private var WEIGHT = "WEIGHT" private var STEPS = "STEPS" + private var AGGREGATE_STEP_COUNT = "AGGREGATE_STEP_COUNT" private var ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" private var HEART_RATE = "HEART_RATE" private var BODY_TEMPERATURE = "BODY_TEMPERATURE" @@ -56,11 +62,14 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME) channel?.setMethodCallHandler(this) + threadPoolExecutor = Executors.newFixedThreadPool(4) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel = null activity = null + threadPoolExecutor!!.shutdown() + threadPoolExecutor = null } // This static function is optional and equivalent to onAttachedToEngine. It supports the old @@ -142,6 +151,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl HEIGHT -> DataType.TYPE_HEIGHT WEIGHT -> DataType.TYPE_WEIGHT STEPS -> DataType.TYPE_STEP_COUNT_DELTA + AGGREGATE_STEP_COUNT -> DataType.AGGREGATE_STEP_COUNT_DELTA ACTIVE_ENERGY_BURNED -> DataType.TYPE_CALORIES_EXPENDED HEART_RATE -> DataType.TYPE_HEART_RATE_BPM BODY_TEMPERATURE -> HealthDataTypes.TYPE_BODY_TEMPERATURE @@ -250,8 +260,6 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ) } val fitnessOptions = typesBuilder.build() - - try { val googleSignInAccount = GoogleSignIn.getAccountForExtension(activity!!.applicationContext, fitnessOptions) Fitness.getHistoryClient(activity!!.applicationContext, googleSignInAccount) @@ -269,6 +277,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl } } + + private fun getData(call: MethodCall, result: Result) { if (activity == null) { result.success(null) @@ -282,148 +292,158 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl // Look up data type and unit for the type key val dataType = keyToHealthDataType(type) val field = getField(type) + val typesBuilder = FitnessOptions.builder() + typesBuilder.addDataType(dataType) + if (dataType == DataType.TYPE_SLEEP_SEGMENT) { + typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ) + } + val fitnessOptions = typesBuilder.build() + val googleSignInAccount = GoogleSignIn.getAccountForExtension(activity!!.applicationContext, fitnessOptions) - /// Start a new thread for doing a GoogleFit data lookup - thread { - try { - val typesBuilder = FitnessOptions.builder() - typesBuilder.addDataType(dataType) - if (dataType == DataType.TYPE_SLEEP_SEGMENT) { - typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ) - } - val fitnessOptions = typesBuilder.build() - val googleSignInAccount = GoogleSignIn.getAccountForExtension(activity!!.applicationContext, fitnessOptions) - - if (dataType != DataType.TYPE_SLEEP_SEGMENT) { - val response = Fitness.getHistoryClient(activity!!.applicationContext, googleSignInAccount) - .readData(DataReadRequest.Builder() + if (dataType != DataType.TYPE_SLEEP_SEGMENT) { + Fitness.getHistoryClient(activity!!.applicationContext, googleSignInAccount) + .readData(DataReadRequest.Builder() .read(dataType) .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS) .build()) + .addOnSuccessListener (threadPoolExecutor!!, dataHandler(dataType, field, result)) + .addOnFailureListener(errHandler(result)) + } else { + // request to the sessions for sleep data + val request = SessionReadRequest.Builder() + .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS) + .enableServerQueries() + .readSessionsFromAllApps() + .includeSleepSessions() + .build() + Fitness.getSessionsClient(activity!!.applicationContext, googleSignInAccount) + .readSession(request) + .addOnSuccessListener(threadPoolExecutor!!, sleepDataHandler(type, result)) + .addOnFailureListener(errHandler(result)) + } + + } + + private fun dataHandler(dataType: DataType, field: Field, result: Result) = + OnSuccessListener { response: DataReadResponse -> + /// Fetch all data points for the specified DataType + val dataSet = response.getDataSet(dataType) + /// For each data point, extract the contents and send them to Flutter, along with date and unit. + val healthData = dataSet.dataPoints.mapIndexed { _, dataPoint -> + return@mapIndexed hashMapOf( + "value" to getHealthDataValue(dataPoint, field), + "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS), + "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS), + "source_name" to (dataPoint.originalDataSource.appPackageName + ?: (dataPoint.originalDataSource.device?.model + ?: "")), + "source_id" to dataPoint.originalDataSource.streamIdentifier + ) + } + activity!!.runOnUiThread { result.success(healthData) } + } + + private fun errHandler(result: Result) = OnFailureListener { exception -> + activity!!.runOnUiThread { result.success(null) } + Log.i("FLUTTER_HEALTH::ERROR", exception.message ?: "unknown error") + Log.i("FLUTTER_HEALTH::ERROR", exception.stackTrace.toString()) + } - /// Fetch all data points for the specified DataType - val dataPoints = Tasks.await(response).getDataSet(dataType) - - /// For each data point, extract the contents and send them to Flutter, along with date and unit. - val healthData = dataPoints.dataPoints.mapIndexed { _, dataPoint -> - return@mapIndexed hashMapOf( - "value" to getHealthDataValue(dataPoint, field), - "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS), - "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS), - "source_name" to (dataPoint.originalDataSource.appPackageName ?: (dataPoint.originalDataSource.device?.model ?: "" )), - "source_id" to dataPoint.originalDataSource.streamIdentifier + private fun sleepDataHandler(type: String, result: Result) = + OnSuccessListener { response: SessionReadResponse -> + val healthData: MutableList> = mutableListOf() + for (session in response.sessions) { + + // Return sleep time in Minutes if requested ASLEEP data + if (type == SLEEP_ASLEEP) { + healthData.add( + hashMapOf( + "value" to session.getEndTime(TimeUnit.MINUTES) - session.getStartTime(TimeUnit.MINUTES), + "date_from" to session.getStartTime(TimeUnit.MILLISECONDS), + "date_to" to session.getEndTime(TimeUnit.MILLISECONDS), + "unit" to "MINUTES", + "source_name" to session.appPackageName, + "source_id" to session.identifier + ) ) } - activity!!.runOnUiThread { result.success(healthData) } - } else { - // request to the sessions for sleep data - val request = SessionReadRequest.Builder() - .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS) - .enableServerQueries() - .readSessionsFromAllApps() - .includeSleepSessions() - .build() - Fitness.getSessionsClient(activity!!.applicationContext, googleSignInAccount) - .readSession(request) - .addOnSuccessListener { response -> - var healthData: MutableList> = mutableListOf() - for (session in response.sessions) { - - // Return sleep time in Minutes if requested ASLEEP data - if (type == SLEEP_ASLEEP) { + if (type == SLEEP_IN_BED) { + val dataSets = response.getDataSet(session) + + // If the sleep session has finer granularity sub-components, extract them: + if (dataSets.isNotEmpty()) { + for (dataSet in dataSets) { + for (dataPoint in dataSet.dataPoints) { + // searching OUT OF BED data + if (dataPoint.getValue(Field.FIELD_SLEEP_SEGMENT_TYPE) + .asInt() != 3 + ) { healthData.add( - hashMapOf( - "value" to session.getEndTime(TimeUnit.MINUTES) - session.getStartTime(TimeUnit.MINUTES), - "date_from" to session.getStartTime(TimeUnit.MILLISECONDS), - "date_to" to session.getEndTime(TimeUnit.MILLISECONDS), - "unit" to "MINUTES", - "source_name" to session.appPackageName, - "source_id" to session.identifier - ) - ) - } - // Returns time spent in bed in Minutes - if (type == SLEEP_IN_BED) { - val dataSets = response.getDataSet(session) - - // If the sleep session has finer granularity sub-components, extract them: - if( dataSets.isNotEmpty()){ - for (dataSet in dataSets) { - for (dataPoint in dataSet.dataPoints) { - // searching OUT OF BED data - if (dataPoint.getValue(Field.FIELD_SLEEP_SEGMENT_TYPE).asInt() != 3) { - healthData.add( - hashMapOf( - "value" to dataPoint.getEndTime(TimeUnit.MINUTES) - dataPoint.getStartTime(TimeUnit.MINUTES), - "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS), - "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS), - "unit" to "MINUTES", - "source_name" to (dataPoint.originalDataSource.appPackageName - ?: (dataPoint.originalDataSource.device?.model - ?: "unknown")), - "source_id" to dataPoint.originalDataSource.streamIdentifier - ) - ) - } - } - } - } else { - healthData.add( - hashMapOf( - "value" to session.getEndTime(TimeUnit.MINUTES) - session.getStartTime(TimeUnit.MINUTES), - "date_from" to session.getStartTime(TimeUnit.MILLISECONDS), - "date_to" to session.getEndTime(TimeUnit.MILLISECONDS), - "unit" to "MINUTES", - "source_name" to session.appPackageName, - "source_id" to session.identifier - ) + hashMapOf( + "value" to dataPoint.getEndTime(TimeUnit.MINUTES) - dataPoint.getStartTime( + TimeUnit.MINUTES + ), + "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS), + "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS), + "unit" to "MINUTES", + "source_name" to (dataPoint.originalDataSource.appPackageName + ?: (dataPoint.originalDataSource.device?.model + ?: "unknown")), + "source_id" to dataPoint.originalDataSource.streamIdentifier ) - } - } - - // If the sleep session has finer granularity sub-components, extract them: - if (type == SLEEP_AWAKE) { - val dataSets = response.getDataSet(session) - for (dataSet in dataSets) { - for (dataPoint in dataSet.dataPoints) { - // searching SLEEP AWAKE data - if (dataPoint.getValue(Field.FIELD_SLEEP_SEGMENT_TYPE).asInt() == 1) { - healthData.add( - hashMapOf( - "value" to dataPoint.getEndTime(TimeUnit.MINUTES) - dataPoint.getStartTime(TimeUnit.MINUTES), - "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS), - "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS), - "unit" to "MINUTES", - "source_name" to (dataPoint.originalDataSource.appPackageName - ?: (dataPoint.originalDataSource.device?.model - ?: "unknown")), - "source_id" to dataPoint.originalDataSource.streamIdentifier - ) - ) - } - } - } + ) } } - activity!!.runOnUiThread { result.success(healthData) } } - .addOnFailureListener { exception -> - activity!!.runOnUiThread { result.success(null) } - Log.i("FLUTTER_HEALTH::ERROR", exception.message ?: "unknown error") - Log.i("FLUTTER_HEALTH::ERROR", exception.stackTrace.toString()) + } else { + healthData.add( + hashMapOf( + "value" to session.getEndTime(TimeUnit.MINUTES) - session.getStartTime( + TimeUnit.MINUTES + ), + "date_from" to session.getStartTime(TimeUnit.MILLISECONDS), + "date_to" to session.getEndTime(TimeUnit.MILLISECONDS), + "unit" to "MINUTES", + "source_name" to session.appPackageName, + "source_id" to session.identifier + ) + ) + } + } + + if (type == SLEEP_AWAKE) { + val dataSets = response.getDataSet(session) + for (dataSet in dataSets) { + for (dataPoint in dataSet.dataPoints) { + // searching SLEEP AWAKE data + if (dataPoint.getValue(Field.FIELD_SLEEP_SEGMENT_TYPE).asInt() == 1) { + healthData.add( + hashMapOf( + "value" to dataPoint.getEndTime(TimeUnit.MINUTES) - dataPoint.getStartTime(TimeUnit.MINUTES), + "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS), + "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS), + "unit" to "MINUTES", + "source_name" to (dataPoint.originalDataSource.appPackageName + ?: (dataPoint.originalDataSource.device?.model + ?: "unknown")), + "source_id" to dataPoint.originalDataSource.streamIdentifier + ) + ) + } } + } + } } - } catch (e3: Exception) { - activity!!.runOnUiThread { result.success(null) } + activity!!.runOnUiThread { result.success(healthData) } } - } - } + + + private fun callToHealthTypes(call: MethodCall): FitnessOptions { val typesBuilder = FitnessOptions.builder() val args = call.arguments as HashMap<*, *> -//<<<<<<< metric val types = (args["types"] as? ArrayList<*>)?.filterIsInstance() val permissions = (args["permissions"] as? ArrayList<*>)?.filterIsInstance() @@ -443,15 +463,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl } else -> throw IllegalArgumentException("Unknown access type $access") } - if (typeKey == SLEEP_ASLEEP || typeKey == SLEEP_AWAKE) { -//======= - val types = args["types"] as ArrayList<*> - for (typeKey in types) { - if (typeKey !is String) continue - typesBuilder.addDataType(keyToHealthDataType(typeKey), FitnessOptions.ACCESS_READ) - typesBuilder.addDataType(keyToHealthDataType(typeKey), FitnessOptions.ACCESS_WRITE) if (typeKey == SLEEP_ASLEEP || typeKey == SLEEP_AWAKE || typeKey == SLEEP_IN_BED) { -//>>>>>>> master typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ) when (access) { 0 -> typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ) @@ -508,12 +520,79 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl } } + private fun getTotalStepsInInterval(call: MethodCall, result: Result) { + val start = call.argument("startDate")!! + val end = call.argument("endDate")!! + + val activity = activity ?: return + + val stepsDataType = keyToHealthDataType(STEPS) + val aggregatedDataType = keyToHealthDataType(AGGREGATE_STEP_COUNT) + + val fitnessOptions = FitnessOptions.builder() + .addDataType(stepsDataType) + .addDataType(aggregatedDataType) + .build() + val gsa = GoogleSignIn.getAccountForExtension(activity, fitnessOptions) + + val ds = DataSource.Builder() + .setAppPackageName("com.google.android.gms") + .setDataType(stepsDataType) + .setType(DataSource.TYPE_DERIVED) + .setStreamName("estimated_steps") + .build() + + val duration = (end - start).toInt() + + val request = DataReadRequest.Builder() + .aggregate(ds) + .bucketByTime(duration, TimeUnit.MILLISECONDS) + .setTimeRange(start, end, TimeUnit.MILLISECONDS) + .build() + + Fitness.getHistoryClient(activity, gsa).readData(request) + .addOnFailureListener(errHandler(result)) + .addOnSuccessListener(threadPoolExecutor!!, getStepsInRange(start, end, aggregatedDataType, result)) + + } + + + private fun getStepsInRange(start: Long, end: Long, aggregatedDataType: DataType , result: Result) = + OnSuccessListener { response: DataReadResponse -> + + val map = HashMap() // need to return to Dart so can't use sparse array + for (bucket in response.buckets) { + val dp = bucket.dataSets.firstOrNull()?.dataPoints?.firstOrNull() + if (dp != null) { + print(dp) + + val count = dp.getValue(aggregatedDataType.fields[0]) + + val startTime = dp.getStartTime(TimeUnit.MILLISECONDS) + val startDate = Date(startTime) + val endDate = Date(dp.getEndTime(TimeUnit.MILLISECONDS)) + Log.i("FLUTTER_HEALTH::SUCCESS", "returning $count steps for $startDate - $endDate") + map[startTime] = count.asInt() + } else { + val startDay = Date(start) + val endDay = Date(end) + Log.i("FLUTTER_HEALTH::ERROR", "no steps for $startDay - $endDay") + } + } + + assert(map.size <= 1) { "getTotalStepsInInterval should return only one interval. Found: ${map.size}" } + activity!!.runOnUiThread { + result.success(map.values.firstOrNull()) + } + } + /// Handle calls from the MethodChannel override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { "requestAuthorization" -> requestAuthorization(call, result) "getData" -> getData(call, result) "writeData" -> writeData(call, result) + "getTotalStepsInInterval" -> getTotalStepsInInterval(call, result) "hasPermissions" -> hasPermissions(call, result) else -> result.notImplemented() } diff --git a/packages/health/example/.flutter-plugins-dependencies b/packages/health/example/.flutter-plugins-dependencies index aac5d2cc2..7e1540f6d 100644 --- a/packages/health/example/.flutter-plugins-dependencies +++ b/packages/health/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_macos-2.2.0/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_web-2.1.0/","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-11-18 09:57:35.099678","version":"2.5.3"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_macos-2.2.0/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_web-2.1.0/","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2022-01-09 21:27:45.012627","version":"2.5.3"} \ No newline at end of file diff --git a/packages/health/example/ios/Flutter/flutter_export_environment.sh b/packages/health/example/ios/Flutter/flutter_export_environment.sh index c00f81a61..671cbf9db 100755 --- a/packages/health/example/ios/Flutter/flutter_export_environment.sh +++ b/packages/health/example/ios/Flutter/flutter_export_environment.sh @@ -3,11 +3,12 @@ export "FLUTTER_ROOT=/Users/bardram/dev/flutter" export "FLUTTER_APPLICATION_PATH=/Users/bardram/dev/flutter-plugins/packages/health/example" export "COCOAPODS_PARALLEL_CODE_SIGN=true" -export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_TARGET=/Users/bardram/dev/flutter-plugins/packages/health/example/lib/main.dart" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=1.0.0" -export "FLUTTER_BUILD_NUMBER=1" +export "FLUTTER_BUILD_NAME=3.4.0" +export "FLUTTER_BUILD_NUMBER=3.4.0" +export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" export "DART_OBFUSCATION=false" -export "TRACK_WIDGET_CREATION=false" +export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=.packages" +export "PACKAGE_CONFIG=/Users/bardram/dev/flutter-plugins/packages/health/example/.dart_tool/package_config.json" diff --git a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj index cfc875d1f..b6fb61701 100644 --- a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D6912A28277CD12C0012EA21 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -107,6 +108,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + D6912A28277CD12C0012EA21 /* RunnerDebug.entitlements */, 82F0E64E2375BDAE0022096E /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -174,7 +176,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 59TCTNUBMQ; + DevelopmentTeam = 8TB3T6MAZG; LastSwiftMigration = 0910; ProvisioningStyle = Automatic; SystemCapabilities = { @@ -514,11 +516,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 59TCTNUBMQ; + DEVELOPMENT_TEAM = 8TB3T6MAZG; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -531,7 +533,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.example; + PRODUCT_BUNDLE_IDENTIFIER = "carp-health-example"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/health/example/ios/Runner/RunnerDebug.entitlements similarity index 66% rename from packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/health/example/ios/Runner/RunnerDebug.entitlements index 18d981003..2ab14a262 100644 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/packages/health/example/ios/Runner/RunnerDebug.entitlements @@ -2,7 +2,9 @@ - IDEDidComputeMac32BitWarning + com.apple.developer.healthkit + com.apple.developer.healthkit.access + diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart index 5b57fef8a..b14045c1a 100644 --- a/packages/health/example/lib/main.dart +++ b/packages/health/example/lib/main.dart @@ -4,11 +4,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:health/health.dart'; -void main() => runApp(MyApp()); +void main() => runApp(HealthApp()); -class MyApp extends StatefulWidget { +class HealthApp extends StatefulWidget { @override - _MyAppState createState() => _MyAppState(); + _HealthAppState createState() => _HealthAppState(); } enum AppState { @@ -19,43 +19,106 @@ enum AppState { AUTH_NOT_GRANTED, DATA_ADDED, DATA_NOT_ADDED, + STEPS_READY, } -class _MyAppState extends State { +class _HealthAppState extends State { List _healthDataList = []; AppState _state = AppState.DATA_NOT_FETCHED; int _nofSteps = 10; double _mgdl = 10.0; - @override - void initState() { - super.initState(); + // create a HealthFactory for use in the app + HealthFactory health = HealthFactory(); + + /// Fetch data points from the health plugin and show them in the app. + Future fetchData() async { + setState(() => _state = AppState.FETCHING_DATA); + + // define the types to get + final types = [ + HealthDataType.STEPS, + HealthDataType.WEIGHT, + HealthDataType.HEIGHT, + HealthDataType.BLOOD_GLUCOSE, + // Uncomment this line on iOS - only available on iOS + // HealthDataType.DISTANCE_WALKING_RUNNING, + ]; + + // with coresponsing permissions + final permissions = [ + HealthDataAccess.READ, + HealthDataAccess.READ, + HealthDataAccess.READ, + HealthDataAccess.READ, + ]; + + // get data within the last 24 hours + final now = DateTime.now(); + final yesterday = now.subtract(Duration(days: 1)); + + // requesting access to the data types before reading them + // note that strictly speaking, the [permissions] are not + // needed, since we only want READ access. + bool requested = + await health.requestAuthorization(types, permissions: permissions); + + if (requested) { + try { + // fetch health data + List healthData = + await health.getHealthDataFromTypes(yesterday, now, types); + + // save all the new data points (only the first 100) + _healthDataList.addAll((healthData.length < 100) + ? healthData + : healthData.sublist(0, 100)); + } catch (error) { + print("Exception in getHealthDataFromTypes: $error"); + } + + // filter out duplicates + _healthDataList = HealthFactory.removeDuplicates(_healthDataList); + + // print the results + _healthDataList.forEach((x) => print(x)); + + // update the UI to display the results + setState(() { + _state = + _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY; + }); + } else { + print("Authorization not granted"); + setState(() => _state = AppState.DATA_NOT_FETCHED); + } } + /// Add some random health data. Future addData() async { - HealthFactory health = HealthFactory(); - - final time = DateTime.now(); - final ago = time.add(Duration(minutes: -5)); + final now = DateTime.now(); + final earlier = now.subtract(Duration(minutes: 5)); _nofSteps = Random().nextInt(10); final types = [HealthDataType.STEPS, HealthDataType.BLOOD_GLUCOSE]; final rights = [HealthDataAccess.WRITE, HealthDataAccess.WRITE]; - final permissions = [HealthDataAccess.READ_WRITE, HealthDataAccess.READ_WRITE]; + final permissions = [ + HealthDataAccess.READ_WRITE, + HealthDataAccess.READ_WRITE + ]; bool? hasPermissions = await HealthFactory.hasPermissions(types, permissions: rights); if (hasPermissions == false) { await health.requestAuthorization(types, permissions: permissions); } - //_nofSteps = Random().nextInt(10); _mgdl = Random().nextInt(10) * 1.0; bool success = await health.writeHealthData( - _nofSteps.toDouble(), HealthDataType.STEPS, ago, time); + _nofSteps.toDouble(), HealthDataType.STEPS, earlier, now); if (success) { success = await health.writeHealthData( - _mgdl, HealthDataType.BLOOD_GLUCOSE, time, time); + _mgdl, HealthDataType.BLOOD_GLUCOSE, now, now); } setState(() { @@ -63,64 +126,28 @@ class _MyAppState extends State { }); } - /// Fetch data from the healt plugin and print it - Future fetchData() async { - // get everything from midnight until now - DateTime startDate = DateTime(2020, 11, 07, 0, 0, 0); - DateTime endDate = DateTime(2025, 11, 07, 23, 59, 59); - - HealthFactory health = HealthFactory(); - - // define the types to get - List types = [ - HealthDataType.STEPS, - HealthDataType.WEIGHT, - HealthDataType.HEIGHT, - HealthDataType.BLOOD_GLUCOSE, - // Uncomment this line on iOS. This type is supported ONLY on Android! - // HealthDataType.DISTANCE_WALKING_RUNNING, - ]; - - List permissions = [ - HealthDataAccess.READ_WRITE, - HealthDataAccess.READ, - HealthDataAccess.READ, - HealthDataAccess.READ_WRITE, - ]; + /// Fetch steps from the health plugin and show them in the app. + Future fetchStepData() async { + int? steps; - setState(() => _state = AppState.FETCHING_DATA); + // get steps for today (i.e., since midnight) + final now = DateTime.now(); + final midnight = DateTime(now.year, now.month, now.day); - // you MUST request access to the data types before reading them - bool requested = await health.requestAuthorization(types, permissions: permissions); - int steps = 0; + bool requested = await health.requestAuthorization([HealthDataType.STEPS]); if (requested) { try { - // fetch new data - List healthData = - await health.getHealthDataFromTypes(startDate, endDate, types); - - // save all the new data points - _healthDataList.addAll(healthData); - } catch (e) { - print("Caught exception in getHealthDataFromTypes: $e"); + steps = await health.getTotalStepsInInterval(midnight, now); + } catch (error) { + print("Caught exception in getTotalStepsInInterval: $error"); } - // filter out duplicates - _healthDataList = HealthFactory.removeDuplicates(_healthDataList); - - // print the results - _healthDataList.forEach((x) { - print("Data point: $x"); - steps += x.value.round(); - }); - - print("Steps: $steps"); + print('Total number of steps: $steps'); - // update the UI to display the results setState(() { - _state = - _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY; + _nofSteps = (steps == null) ? 0 : steps; + _state = (steps == null) ? AppState.NO_DATA : AppState.STEPS_READY; }); } else { print("Authorization not granted"); @@ -163,22 +190,27 @@ class _MyAppState extends State { return Column( children: [ Text('Press the download button to fetch data.'), - Text('Press the plus button to insert some random data.') + Text('Press the plus button to insert some random data.'), + Text('Press the walking button to get total step count.'), ], mainAxisAlignment: MainAxisAlignment.center, ); } Widget _authorizationNotGranted() { - return Text('''Authorization not given. - For Android please check your OAUTH2 client ID is correct in Google Developer Console. - For iOS check your permissions in Apple Health.'''); + return Text('Authorization not given. ' + 'For Android please check your OAUTH2 client ID is correct in Google Developer Console. ' + 'For iOS check your permissions in Apple Health.'); } Widget _dataAdded() { return Text('$_nofSteps steps and $_mgdl mgdl are inserted successfully!'); } + Widget _stepsFetched() { + return Text('Total number of steps: $_nofSteps'); + } + Widget _dataNotAdded() { return Text('Failed to add data'); } @@ -194,6 +226,8 @@ class _MyAppState extends State { return _authorizationNotGranted(); else if (_state == AppState.DATA_ADDED) return _dataAdded(); + else if (_state == AppState.STEPS_READY) + return _stepsFetched(); else if (_state == AppState.DATA_NOT_ADDED) return _dataNotAdded(); return _contentNotFetched(); @@ -204,7 +238,7 @@ class _MyAppState extends State { return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('Plugin example app'), + title: const Text('Health Example'), actions: [ IconButton( icon: Icon(Icons.file_download), @@ -213,10 +247,17 @@ class _MyAppState extends State { }, ), IconButton( - onPressed: () { - addData(); - }, - icon: Icon(Icons.add)) + onPressed: () { + addData(); + }, + icon: Icon(Icons.add), + ), + IconButton( + onPressed: () { + fetchStepData(); + }, + icon: Icon(Icons.nordic_walking), + ) ], ), body: Center( diff --git a/packages/health/example/pubspec.yaml b/packages/health/example/pubspec.yaml index fe5d9380a..5005c1c69 100644 --- a/packages/health/example/pubspec.yaml +++ b/packages/health/example/pubspec.yaml @@ -1,6 +1,7 @@ name: health_example description: Demonstrates how to use the health plugin. publish_to: 'none' +version: 3.4.0 environment: sdk: ">=2.12.0-0 <3.0.0" diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index ae6b1c171..7102bb77c 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -76,6 +76,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { getData(call: call, result: result) } + /// Handle getTotalStepsInInterval + else if (call.method.elementsEqual("getTotalStepsInInterval")){ + getTotalStepsInInterval(call: call, result: result) + } + /// Handle writeData else if (call.method.elementsEqual("writeData")){ try! writeData(call: call, result: result) @@ -184,9 +189,15 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { print("Successfully called writeData with value of \(value) and type of \(type)") - let quantity = HKQuantity(unit: unitLookUp(key: type), doubleValue: value) - - let sample = HKQuantitySample(type: dataTypeLookUp(key: type) as! HKQuantityType, quantity: quantity, start: dateFrom, end: dateTo) + let sample: HKObject + + if (unitLookUp(key: type) == HKUnit.init(from: "")) { + sample = HKCategorySample(type: dataTypeLookUp(key: type) as! HKCategoryType, value: Int(value), start: dateFrom, end: dateTo) + } else { + let quantity = HKQuantity(unit: unitLookUp(key: type), doubleValue: value) + + sample = HKQuantitySample(type: dataTypeLookUp(key: type) as! HKQuantityType, quantity: quantity, start: dateFrom, end: dateTo) + } HKHealthStore().save(sample, withCompletion: { (success, error) in if let err = error { @@ -219,19 +230,19 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { switch samplesOrNil { case let (samples as [HKQuantitySample]) as Any: + let dictionaries = samples.map { sample -> NSDictionary in + let unit = self.unitLookUp(key: dataTypeKey) + return [ + "uuid": "\(sample.uuid)", + "value": sample.quantity.doubleValue(for: unit), + "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), + "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), + "source_id": sample.sourceRevision.source.bundleIdentifier, + "source_name": sample.sourceRevision.source.name + ] + } DispatchQueue.main.async { - result(samples.map { sample -> NSDictionary in - let unit = self.unitLookUp(key: dataTypeKey) - - return [ - "uuid": "\(sample.uuid)", - "value": sample.quantity.doubleValue(for: unit), - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name - ] - }) + result(dictionaries) } case var (samplesCategory as [HKCategorySample]) as Any: @@ -244,32 +255,35 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { if (dataTypeKey == self.SLEEP_ASLEEP) { samplesCategory = samplesCategory.filter { $0.value == 1 } } - + let categories = samplesCategory.map { sample -> NSDictionary in + return [ + "uuid": "\(sample.uuid)", + "value": sample.value, + "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), + "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), + "source_id": sample.sourceRevision.source.bundleIdentifier, + "source_name": sample.sourceRevision.source.name + ] + } DispatchQueue.main.async { - result(samplesCategory.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "value": sample.value, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name - ] - }) + result(categories) } case let (samplesWorkout as [HKWorkout]) as Any: + + let dictionaries = samplesWorkout.map { sample -> NSDictionary in + return [ + "uuid": "\(sample.uuid)", + "value": Int(sample.duration), + "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), + "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), + "source_id": sample.sourceRevision.source.bundleIdentifier, + "source_name": sample.sourceRevision.source.name + ] + } + DispatchQueue.main.async { - result(samplesWorkout.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "value": Int(sample.duration), - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name - ] - }) + result(dictionaries) } default: @@ -282,6 +296,48 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { HKHealthStore().execute(query) } + func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + let startDate = (arguments?["startDate"] as? NSNumber) ?? 0 + let endDate = (arguments?["endDate"] as? NSNumber) ?? 0 + + // Convert dates from milliseconds to Date() + let dateFrom = Date(timeIntervalSince1970: startDate.doubleValue / 1000) + let dateTo = Date(timeIntervalSince1970: endDate.doubleValue / 1000) + + let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)! + let predicate = HKQuery.predicateForSamples(withStart: dateFrom, end: dateTo, options: .strictStartDate) + + let query = HKStatisticsQuery(quantityType: sampleType, + quantitySamplePredicate: predicate, + options: .cumulativeSum) { query, queryResult, error in + + guard let queryResult = queryResult else { + let error = error! as NSError + print("Error getting total steps in interval \(error.localizedDescription)") + + DispatchQueue.main.async { + result(nil) + } + return + } + + var steps = 0.0 + + if let quantity = queryResult.sumQuantity() { + let unit = HKUnit.count() + steps = quantity.doubleValue(for: unit) + } + + let totalSteps = Int(steps) + DispatchQueue.main.async { + result(totalSteps) + } + } + + HKHealthStore().execute(query) + } + func unitLookUp(key: String) -> HKUnit { guard let unit = unitDict[key] else { return HKUnit.count() diff --git a/packages/health/lib/health.dart b/packages/health/lib/health.dart index ac6763d00..76679e077 100644 --- a/packages/health/lib/health.dart +++ b/packages/health/lib/health.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:io' show Platform; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; @@ -11,4 +12,3 @@ part 'src/data_types.dart'; part 'src/functions.dart'; part 'src/health_data_point.dart'; part 'src/health_factory.dart'; -part 'src/units.dart'; diff --git a/packages/health/lib/src/data_types.dart b/packages/health/lib/src/data_types.dart index 6a50386b9..ea340003d 100644 --- a/packages/health/lib/src/data_types.dart +++ b/packages/health/lib/src/data_types.dart @@ -48,6 +48,7 @@ enum HealthDataAccess { WRITE, READ_WRITE, } + /// List of data types available on iOS const List _dataTypeKeysIOS = [ HealthDataType.ACTIVE_ENERGY_BURNED, @@ -109,7 +110,7 @@ const List _dataTypeKeysAndroid = [ HealthDataType.WATER, ]; -/// Map a [HealthDataType] to a [HealthDataUnit]. +/// Maps a [HealthDataType] to a [HealthDataUnit]. const Map _dataTypeToUnit = { HealthDataType.ACTIVE_ENERGY_BURNED: HealthDataUnit.CALORIES, HealthDataType.BASAL_ENERGY_BURNED: HealthDataUnit.CALORIES, @@ -146,7 +147,7 @@ const Map _dataTypeToUnit = { HealthDataType.EXERCISE_TIME: HealthDataUnit.MINUTES, HealthDataType.WORKOUT: HealthDataUnit.MINUTES, - /// Heart Rate events (specific to Apple Watch) + // Heart Rate events (specific to Apple Watch) HealthDataType.HIGH_HEART_RATE_EVENT: HealthDataUnit.NO_UNIT, HealthDataType.LOW_HEART_RATE_EVENT: HealthDataUnit.NO_UNIT, HealthDataType.IRREGULAR_HEART_RATE_EVENT: HealthDataUnit.NO_UNIT, @@ -211,3 +212,23 @@ const PlatformTypeJsonValue = { PlatformType.IOS: 'ios', PlatformType.ANDROID: 'android' }; + +/// List of all [HealthDataPoint] units. +enum HealthDataUnit { + BEATS_PER_MINUTE, + CALORIES, + COUNT, + DEGREE_CELSIUS, + GRAMS, + KILOGRAMS, + METERS, + MILLIGRAM_PER_DECILITER, + MILLIMETER_OF_MERCURY, + MILLISECONDS, + MINUTES, + NO_UNIT, + PERCENTAGE, + SIEMENS, + UNKNOWN_UNIT, + LITER, +} diff --git a/packages/health/lib/src/functions.dart b/packages/health/lib/src/functions.dart index 56f0b7ba1..e58cb306e 100644 --- a/packages/health/lib/src/functions.dart +++ b/packages/health/lib/src/functions.dart @@ -1,20 +1,19 @@ part of health; -/// Custom Exception for the plugin, -/// thrown whenever a Health Data Type is requested, -/// when not available on the current platform -class _HealthException implements Exception { - dynamic _dataType; - String _cause; +/// Custom Exception for the plugin. Used when a Health Data Type is requested, +/// but not available on the current platform. +class HealthException implements Exception { + dynamic dataType; + String cause; - _HealthException(this._dataType, this._cause); + HealthException(this.dataType, this.cause); - String toString() { - return "An exception happened when requesting ${_dataType.toString()}. Cause: $_cause"; - } + String toString() => + "Error requesting health data type '$dataType' - cause: $cause"; } /// Extracts the string value from an enum String _enumToString(enumItem) => enumItem.toString().split('.').last; +/// A list of supported platforms. enum PlatformType { IOS, ANDROID } diff --git a/packages/health/lib/src/health_data_point.dart b/packages/health/lib/src/health_data_point.dart index fea3621db..d4bef4e4e 100644 --- a/packages/health/lib/src/health_data_point.dart +++ b/packages/health/lib/src/health_data_point.dart @@ -1,7 +1,7 @@ part of health; /// A [HealthDataPoint] object corresponds to a data point captures from -/// GoogleFit or Apple HealthKit +/// GoogleFit or Apple HealthKit. class HealthDataPoint { num _value; HealthDataType _type; @@ -66,56 +66,50 @@ class HealthDataPoint { 'source_name': sourceName }; - /// Converts the [HealthDataPoint] to a string + @override String toString() => '${this.runtimeType} - ' 'value: $value, ' 'unit: $unit, ' 'dateFrom: $dateFrom, ' 'dateTo: $dateTo, ' - 'dataType: $type,' - 'platform: $platform' - 'sourceId: $sourceId,' - 'sourceName: $sourceName,'; + 'dataType: $type, ' + 'platform: $platform, ' + 'sourceId: $sourceId, ' + 'sourceName: $sourceName'; - /// Get the quantity value of the data point + /// The quantity value of the data point num get value => _value; - /// Get the start of the datetime interval + /// The start of the time interval DateTime get dateFrom => _dateFrom; - /// Get the end of the datetime interval + /// The end of the time interval DateTime get dateTo => _dateTo; - /// Get the type of the data point + /// The type of the data point HealthDataType get type => _type; - /// Get the unit of the data point + /// The unit of the data point HealthDataUnit get unit => _unit; - /// Get the software platform of the data point - /// (i.e. Android or iOS) + /// The software platform of the data point PlatformType get platform => _platform; - /// Get the data point type as a string + /// The data point type as a string String get typeString => _enumToString(_type); - /// Get the data point unit as a string + /// The data point unit as a string String get unitString => _enumToString(_unit); - /// Get the id of the device from which - /// the data point was extracted + /// The id of the device from which the data point was fetched. String get deviceId => _deviceId; - /// Get the id of the source from which - /// the data point was extracted + /// The id of the source from which the data point was fetched. String get sourceId => _sourceId; - /// Get the name of the source from which - /// the data point was extracted + /// The name of the source from which the data point was fetched. String get sourceName => _sourceName; - /// An equals (==) operator for comparing two data points - /// This makes it possible to remove duplicate data points. @override bool operator ==(Object o) { return o is HealthDataPoint && @@ -130,7 +124,6 @@ class HealthDataPoint { this.sourceName == o.sourceName; } - /// Override required due to overriding the '==' operator @override int get hashCode => toJson().hashCode; } diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart index d86fdc9df..30b7dd04d 100644 --- a/packages/health/lib/src/health_factory.dart +++ b/packages/health/lib/src/health_factory.dart @@ -1,6 +1,15 @@ part of health; -/// Main class for the Plugin +/// Main class for the Plugin. +/// +/// The plugin supports: +/// +/// * handling permissions to access health data using the [hasPermissions], +/// [requestPermissions], [requestAuthorization], [revokePermissions] methods. +/// * reading health data using the [getHealthDataFromTypes] method. +/// * writing health data using the [writeHealthData] method. +/// * accessing total step counts using the [getTotalStepsInInterval] method. +/// * cleaning up dublicate data points via the [removeDuplicates] method. class HealthFactory { static const MethodChannel _channel = MethodChannel('flutter_health'); String? _deviceId; @@ -17,26 +26,26 @@ class HealthFactory { /// Determines if the data types have been granted with the specified access rights. /// - /// Returns: - /// + /// Returns: + /// /// * true - if all of the data types have been granted with the specfied access rights. /// * false - if any of the data types has not been granted with the specified access right(s) /// * null - if it can not be determined if the data types have been granted with the specified access right(s). /// /// Parameters: - /// + /// /// * [types] - List of [HealthDataType] whose permissions are to be checked. - /// * [permissions] - Optional. + /// * [permissions] - Optional. /// + If unspecified, this method checks if each HealthDataType in [types] has been granted READ access. - /// + If specified, this method checks if each [HealthDataType] in [types] has been granted with the access specified in its + /// + If specified, this method checks if each [HealthDataType] in [types] has been granted with the access specified in its /// corresponding entry in this list. The length of this list must be equal to that of [types]. - /// + /// /// Caveat: - /// + /// /// As Apple HealthKit will not disclose if READ access has been granted for a data type due to privacy concern, /// this method can only return null to represent an undertermined status, if it is called on iOS - /// with a READ or READ_WRITE access. - /// + /// with a READ or READ_WRITE access. + /// /// On Android, this function returns true or false, depending on whether the specified access right has been granted. static Future hasPermissions(List types, {List? permissions}) async { @@ -51,8 +60,7 @@ class HealthFactory { : permissions.map((permission) => permission.index).toList(); /// On Android, if BMI is requested, then also ask for weight and height - if (_platformType == PlatformType.ANDROID) - _handleBMI(mTypes, mPermissions); + if (_platformType == PlatformType.ANDROID) _handleBMI(mTypes, mPermissions); return await _channel.invokeMethod('hasPermissions', { "types": mTypes.map((type) => _enumToString(type)).toList(), @@ -71,26 +79,28 @@ class HealthFactory { }); } - /// iOS isn't supported by HealthKit, method does nothing. + /// Revoke permissions obtained earlier. + /// + /// Not supported on iOS and method does nothing. static Future revokePermissions() async { return await _channel.invokeMethod('revokePermissions'); } + /// Requests permissions to access data types in Apple Health or Google Fit. /// - /// Requests permissions to access data types in the HealthKit or Google Fit store. - /// - /// Returns a Future of true if successful, a Future of false otherwise + /// Returns true if successful, false otherwise /// - /// Parameters + /// Parameters: /// /// * [types] - a list of [HealthDataType] which the permissions are requested for. - /// * [permissions] - Optional. + /// * [permissions] - Optional. /// + If unspecified, each [HealthDataType] in [types] is requested for READ [HealthDataAccess]. - /// + If specified, each [HealthDataAccess] in this list is requested for its corresponding indexed + /// + If specified, each [HealthDataAccess] in this list is requested for its corresponding indexed /// entry in [types]. In addition, the length of this list must be equal to that of [types]. - /// - Future requestAuthorization(List types, - {List? permissions}) async { + Future requestAuthorization( + List types, { + List? permissions, + }) async { if (permissions != null && permissions.length != types.length) { throw ArgumentError( 'The length of [types] must be same as that of [permissions].'); @@ -102,18 +112,16 @@ class HealthFactory { growable: true) : permissions.map((permission) => permission.index).toList(); - /// On Android, if BMI is requested, then also ask for weight and height - if (_platformType == PlatformType.ANDROID) - _handleBMI(mTypes, mPermissions); + // on Android, if BMI is requested, then also ask for weight and height + if (_platformType == PlatformType.ANDROID) _handleBMI(mTypes, mPermissions); List keys = mTypes.map((e) => _enumToString(e)).toList(); - final bool isAuthorized = await _channel.invokeMethod( + final bool? isAuthorized = await _channel.invokeMethod( 'requestAuthorization', {'types': keys, "permissions": mPermissions}); - return isAuthorized; + return isAuthorized ?? false; } - static void _handleBMI( - List mTypes, List mPermissions) { + static void _handleBMI(List mTypes, List mPermissions) { final index = mTypes.indexOf(HealthDataType.BODY_MASS_INDEX); if (index != -1 && _platformType == PlatformType.ANDROID) { @@ -159,13 +167,11 @@ class HealthFactory { return bmiHealthPoints; } + /// Saves health data into Apple Health or Google Fit. /// - /// Saves health data into the HealthKit or Google Fit store. - /// - /// Returns a Future of true if successful, a Future of false otherwise + /// Returns true if successful, false otherwise. /// /// Parameters: - /// /// * [value] - the health data's value in double /// * [type] - the value's HealthDataType /// * [startTime] - the start time when this [value] is measured. @@ -174,8 +180,12 @@ class HealthFactory { /// + It must be equal to or later than [startTime]. /// + Simply set [endTime] equal to [startTime] if the [value] is measured only at a specific point in time. /// - Future writeHealthData(double value, HealthDataType type, - DateTime startTime, DateTime endTime) async { + Future writeHealthData( + double value, + HealthDataType type, + DateTime startTime, + DateTime endTime, + ) async { if (startTime.isAfter(endTime)) throw ArgumentError("startTime must be equal or earlier than endTime"); Map args = { @@ -188,14 +198,24 @@ class HealthFactory { return success ?? false; } + /// Fetch a list of health data points based on [types]. Future> getHealthDataFromTypes( - DateTime startDate, DateTime endDate, List types) async { + DateTime startDate, + DateTime endDate, + List types, + ) async { List dataPoints = []; for (var type in types) { final result = await _prepareQuery(startDate, endDate, type); dataPoints.addAll(result); } + + const int threshold = 100; + if (dataPoints.length > threshold) { + return compute(removeDuplicates, dataPoints); + } + return removeDuplicates(dataPoints); } @@ -209,7 +229,7 @@ class HealthFactory { // If not implemented on platform, throw an exception if (!isDataTypeAvailable(dataType)) { - throw _HealthException( + throw HealthException( dataType, 'Not available on platform $_platformType'); } @@ -224,41 +244,58 @@ class HealthFactory { /// The main function for fetching health data Future> _dataQuery( DateTime startDate, DateTime endDate, HealthDataType dataType) async { - // Set parameters for method channel request final args = { 'dataTypeKey': _enumToString(dataType), 'startDate': startDate.millisecondsSinceEpoch, 'endDate': endDate.millisecondsSinceEpoch }; - final unit = _dataTypeToUnit[dataType]!; - final fetchedDataPoints = await _channel.invokeMethod('getData', args); if (fetchedDataPoints != null) { - return fetchedDataPoints.map((e) { - final num value = e['value']; - final DateTime from = - DateTime.fromMillisecondsSinceEpoch(e['date_from']); - final DateTime to = DateTime.fromMillisecondsSinceEpoch(e['date_to']); - final String sourceId = e["source_id"]; - final String sourceName = e["source_name"]; - return HealthDataPoint( - value, - dataType, - unit, - from, - to, - _platformType, - _deviceId!, - sourceId, - sourceName, - ); - }).toList(); + final mesg = { + "dataType": dataType, + "dataPoints": fetchedDataPoints, + "deviceId": _deviceId!, + }; + const thresHold = 100; + // If the no. of data points are larger than the threshold, + // call the compute method to spawn an Isolate to do the parsing in a separate thread. + if (fetchedDataPoints.length > thresHold) { + return compute(_parse, mesg); + } + return _parse(mesg); } else { return []; } } + static List _parse(Map message) { + final dataType = message["dataType"]; + final dataPoints = message["dataPoints"]; + final device = message["deviceId"]; + final unit = _dataTypeToUnit[dataType]!; + final list = dataPoints.map((e) { + final num value = e['value']; + final DateTime from = DateTime.fromMillisecondsSinceEpoch(e['date_from']); + final DateTime to = DateTime.fromMillisecondsSinceEpoch(e['date_to']); + final String sourceId = e["source_id"]; + final String sourceName = e["source_name"]; + return HealthDataPoint( + value, + dataType, + unit, + from, + to, + _platformType, + device, + sourceId, + sourceName, + ); + }).toList(); + + return list; + } + /// Given an array of [HealthDataPoint]s, this method will return the array /// without any duplicates. static List removeDuplicates(List points) { @@ -269,6 +306,7 @@ class HealthFactory { for (var s in unique) { if (s == p) { seenBefore = true; + break; } } if (!seenBefore) { @@ -277,4 +315,23 @@ class HealthFactory { } return unique; } + + /// Get the total numbner of steps within a specific time period. + /// Returns null if not successful. + /// + /// Is a fix according to https://stackoverflow.com/questions/29414386/step-count-retrieved-through-google-fit-api-does-not-match-step-count-displayed/29415091#29415091 + Future getTotalStepsInInterval( + DateTime startDate, + DateTime endDate, + ) async { + final args = { + 'startDate': startDate.millisecondsSinceEpoch, + 'endDate': endDate.millisecondsSinceEpoch + }; + final stepsCount = await _channel.invokeMethod( + 'getTotalStepsInInterval', + args, + ); + return stepsCount; + } } diff --git a/packages/health/lib/src/units.dart b/packages/health/lib/src/units.dart deleted file mode 100644 index c24a56fe8..000000000 --- a/packages/health/lib/src/units.dart +++ /dev/null @@ -1,21 +0,0 @@ -part of health; - -/// List of all [HealthDataPoint] units. -enum HealthDataUnit { - BEATS_PER_MINUTE, - CALORIES, - COUNT, - DEGREE_CELSIUS, - GRAMS, - KILOGRAMS, - METERS, - MILLIGRAM_PER_DECILITER, - MILLIMETER_OF_MERCURY, - MILLISECONDS, - MINUTES, - NO_UNIT, - PERCENTAGE, - SIEMENS, - UNKNOWN_UNIT, - LITER, -} diff --git a/packages/health/pubspec.yaml b/packages/health/pubspec.yaml index 5ee963dce..4ee7fc766 100644 --- a/packages/health/pubspec.yaml +++ b/packages/health/pubspec.yaml @@ -1,6 +1,6 @@ name: health description: Wrapper for the iOS HealthKit and Android GoogleFit services. -version: 3.3.1 +version: 3.4.3 homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages/health environment: diff --git a/packages/mobility_features/.flutter-plugins-dependencies b/packages/mobility_features/.flutter-plugins-dependencies deleted file mode 100644 index 10fdb49b5..000000000 --- a/packages/mobility_features/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider","path":"/Users/tnni/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.11/","dependencies":[]}],"android":[{"name":"path_provider","path":"/Users/tnni/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.11/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/tnni/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/tnni/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+1/","dependencies":[]}],"windows":[],"web":[]},"dependencyGraph":[{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]}],"date_created":"2020-08-14 11:55:36.022296","version":"1.17.5"} \ No newline at end of file diff --git a/packages/mobility_features/CHANGELOG.md b/packages/mobility_features/CHANGELOG.md index 59ab0c864..467662852 100644 --- a/packages/mobility_features/CHANGELOG.md +++ b/packages/mobility_features/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 +* improvement to `MobilityContext` API. +* misc updates to documentation. + ## 3.0.0+2 * update to null-safety * rename of `MobilityFactory` to `MobilityFeatures`and using the standard Dart singleton syntax for `MobilityFeatures()`. diff --git a/packages/mobility_features/README.md b/packages/mobility_features/README.md index f6dca7fbc..e7cfeae9f 100644 --- a/packages/mobility_features/README.md +++ b/packages/mobility_features/README.md @@ -2,37 +2,29 @@ ## Setup -Add the package to your `pubspec.yaml` file and import the package +The Mobility Features package is designed to work independent of the location plugin. You may choose you own location plugin, since you may already use this in your app. -No permissions are required to use the package, however, a location plugin should be used to stream data. +In the example app we use our own plugin [`carp_background_location`](https://pub.dev/packages/carp_background_location) which works on both Android and iOS as of August 2020. However, the +[location](https://pub.dev/packages/location) plugin will also work. The important thing, however, is to make sure that the app runs in the backgound. On Android this is tied to running the app as a foregound service. -We recommend our own plugin [`carp_background_location`](https://pub.dev/packages/carp_background_location) which works on both Android and iOS as of August 2020. +Add the package to your `pubspec.yaml` file and import the package ```dart import 'package:mobility_features/mobility_features.dart'; ``` -### Step 1: Choose parameters -```dart -MobilityFeatures().stopDuration = Duration(seconds: 20); -MobilityFeatures().placeRadius = 50.0; -MobilityFeatures().stopRadius = 5.0; -``` +The plugin works as a singleton and can be accessed using `MobilityFeatures()` in the code. + + +### Step 1 - Configuration of parameters -Optionally, the following configurations can be made, which will influence the algorithms for producing features: +The following configurations can be made, which will influence the algorithms for producing features: * The stop radius should be kept low (5-20 meters) * The place radius somewhat higher (25-50 meters). * The stop duration can also be set to any desired duration, for most cases it should be kept lower than 3 minutes. -Features computation is triggered when the user moves around and change their geo-position by a certain distance (stop distance). -If the stop was long enough (stop duration) the stop will be saved. Places are computed by grouping stops based on distance between them (place radius) - -Common for these parameters is that their value depend on what you are trying to capture: -Low parameter values will make the features more fine-grained but will trigger computation more often and will likely also lead to noisy features. - -For example, given a low stop duration, stopping for a red light in traffic will count as a stop. -Such granularity will be irrelevant for many use cases, but may be useful if questions such as 'do they take the same route to work every day?' are to be answered. +Configuration is done like shown below: ```dart StreamSubscription mobilitySubscription; @@ -40,44 +32,63 @@ MobilityContext _mobilityContext; void initState() { ... - MobilityFeatures().stopDuration = Duration(seconds: 30); - MobilityFeatures().placeRadius = 20; - MobilityFeatures().stopRadius = 5; + MobilityFeatures().stopDuration = Duration(seconds: 20); + MobilityFeatures().placeRadius = 50; + MobilityFeatures().stopRadius = 5.0; } ``` -### Step 2: Set up streaming -Location data collection is not directly supported by this package, for this you have to use a location plugin such as [`carp_background_location`](https://pub.dev/packages/carp_background_location). +Features computation is triggered when the user moves around and change their geo-position by a certain distance (stop distance). +If the stop was long enough (stop duration) the stop will be saved. Places are computed by grouping stops based on distance between them (place radius) + +Common for these parameters is that their value depend on what you are trying to capture. +Low parameter values will make the features more fine-grained but will trigger computation more often and will likely also lead to noisy features. +For example, given a low stop duration, stopping for a red light in traffic will count as a stop. Such granularity will be irrelevant for many use cases, but may be useful if questions such as "Do a user take the same route to work every day?" + -From here, you can to convert from whichever location object is used by the location plugin to a `LocationSample`. +### Step 2 - Set up location streaming -Next, you need to subscribe to the `MobilityFeatures()`'s `contextStream` to be be notified each time a new set of features has been computed. +Collection of location data is not directly supported by this package, for this you have to use a location plugin such as [`carp_background_location`](https://pub.dev/packages/carp_background_location). You can to convert from whichever location object is used by the location plugin to a `LocationSample` object. +Next, you can start listening to location updates and subscribe to the `MobilityFeatures()`'s `contextStream` to be be notified each time a new set of features has been computed. Below is shown an example using the [`carp_background_location`](https://pub.dev/packages/carp_background_location) plugin, where a `LocationDto` stream is converted into a `LocationSample` stream by using a map-function. ```dart -/// Start the streaming of location data and mobility features -void streamInit() async { - /// Get the location data stream (specific to mubs_background_location) + /// Set up streams: + /// * Location streaming to MobilityContext + /// * Subscribe to MobilityContext updates + void streamInit() async { locationStream = LocationManager().locationStream; - locationSubscription = locationStream.listen(onData); - - /// Start the location service (specific to mubs_background_location) + + // subscribe to location stream - in case this is needed in the app + if (locationSubscription != null) locationSubscription.cancel(); + locationSubscription = locationStream.listen(onLocationUpdate); + + // start the location service (specific to carp_background_location) await LocationManager().start(); - /// Convert from [LocationDto] to [LocationSample] - Stream locationSampleStream = locationStream.map((e) => - LocationSample(GeoLocation(e.latitude, e.longitude), DateTime.now())); + // map from [LocationDto] to [LocationSample] + Stream locationSampleStream = locationStream.map( + (location) => LocationSample( + GeoLocation(location.latitude, location.longitude), + DateTime.now())); - /// Provide the MobilityFactory instance with the LocationSample stream + // provide the [MobilityFeatures] instance with the LocationSample stream MobilityFeatures().startListening(locationSampleStream); - - /// Start listening to incoming MobilityContext objects - MobilityFeatures().contextStream.listen(onMobilityContext); -} + + // start listening to incoming MobilityContext objects + mobilitySubscription = + MobilityFeatures().contextStream.listen(onMobilityContext); + } + + /// Called whenever location changes. + void onLocationUpdate(LocationDto dto) { + print(dtoToString(dto)); + } ``` -### Step 3: Handle features +### Step 3 - Handle mobility + A call-back method is used to handle incoming MobilityContext objects: ```dart diff --git a/packages/mobility_features/example/.flutter-plugins-dependencies b/packages/mobility_features/example/.flutter-plugins-dependencies index f0dcf30b4..4c3dcc7f2 100644 --- a/packages/mobility_features/example/.flutter-plugins-dependencies +++ b/packages/mobility_features/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"background_locator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/background_locator-1.6.2+1-beta/","dependencies":[]},{"name":"geolocator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/geolocator-6.2.1/","dependencies":[]},{"name":"location_permissions","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/location_permissions-4.0.0/","dependencies":[]},{"name":"path_provider","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.2/","dependencies":[]}],"android":[{"name":"background_locator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/background_locator-1.6.2+1-beta/","dependencies":[]},{"name":"geolocator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/geolocator-6.2.1/","dependencies":[]},{"name":"location_permissions","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/location_permissions-4.0.0/","dependencies":[]},{"name":"path_provider","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.2/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.0/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.0.0/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.0.1/","dependencies":[]}],"web":[{"name":"geolocator_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/geolocator_web-1.0.1/","dependencies":[]}]},"dependencyGraph":[{"name":"background_locator","dependencies":[]},{"name":"geolocator","dependencies":["geolocator_web"]},{"name":"geolocator_web","dependencies":[]},{"name":"location_permissions","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2021-06-22 17:08:39.248479","version":"2.2.2"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"background_locator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/background_locator-1.6.12/","dependencies":[]},{"name":"path_provider_ios","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_ios-2.0.7/","dependencies":[]}],"android":[{"name":"background_locator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/background_locator-1.6.12/","dependencies":[]},{"name":"path_provider_android","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.11/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.4/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.4/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.0.4/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"background_locator","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_ios","path_provider_linux","path_provider_macos","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_ios","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2022-01-09 19:33:13.611264","version":"2.5.3"} \ No newline at end of file diff --git a/packages/mobility_features/example/README.md b/packages/mobility_features/example/README.md index a13562602..040129780 100644 --- a/packages/mobility_features/example/README.md +++ b/packages/mobility_features/example/README.md @@ -1,16 +1,3 @@ -# example +# Mobility Features Example App -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +This is an example app for the Mobility Features package. diff --git a/packages/mobility_features/example/android/app/build.gradle b/packages/mobility_features/example/android/app/build.gradle index 320be2219..dcb69350b 100644 --- a/packages/mobility_features/example/android/app/build.gradle +++ b/packages/mobility_features/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,7 +40,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" minSdkVersion 16 - targetSdkVersion 28 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/packages/mobility_features/example/android/app/src/main/AndroidManifest.xml b/packages/mobility_features/example/android/app/src/main/AndroidManifest.xml index aa37b748b..bd1b230f6 100644 --- a/packages/mobility_features/example/android/app/src/main/AndroidManifest.xml +++ b/packages/mobility_features/example/android/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ + - --> + + + \ No newline at end of file diff --git a/packages/mobility_features/example/lib/main.dart b/packages/mobility_features/example/lib/main.dart index 3e1eddac4..d63cd2f87 100644 --- a/packages/mobility_features/example/lib/main.dart +++ b/packages/mobility_features/example/lib/main.dart @@ -22,9 +22,7 @@ Widget entry(String key, String value, Icon icon) { )); } -String formatDate(DateTime date) { - return '${date.year}/${date.month}/${date.day}'; -} +String formatDate(DateTime date) => '${date.year}/${date.month}/${date.day}'; String interval(DateTime a, DateTime b) { String pad(int x) => '${x.toString().padLeft(2, '0')}'; @@ -53,11 +51,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Mobility Features Demo', + title: 'Mobility Features Example', theme: ThemeData( primarySwatch: Colors.blue, ), - home: MyHomePage(title: 'Mobility Features Demo'), + home: MyHomePage(title: 'Mobility Features Example'), ); } } @@ -103,46 +101,53 @@ class _MyHomePageState extends State { streamInit(); } - @override - void dispose() { - mobilitySubscription?.cancel(); - super.dispose(); - } - - void onMobilityContext(MobilityContext context) { - print('Context received: ${context.toJson()}'); - setState(() { - _state = AppState.FEATURES_READY; - _mobilityContext = context; - }); - } - /// Set up streams: - /// * Subscribe to stream in case it is already running (Android only) + /// * Location streaming to MobilityContext /// * Subscribe to MobilityContext updates void streamInit() async { locationStream = LocationManager().locationStream; - locationSubscription = locationStream.listen(onData); - // Subscribe if it hasn't been done already - if (locationSubscription != null) { - locationSubscription.cancel(); - } - locationSubscription = locationStream.listen(onData); + // subscribe to location stream - in case this is needed in the app + if (locationSubscription != null) locationSubscription.cancel(); + locationSubscription = locationStream.listen(onLocationUpdate); + + // start the location service (specific to carp_background_location) await LocationManager().start(); - Stream locationSampleStream = locationStream.map((e) => - LocationSample(GeoLocation(e.latitude, e.longitude), DateTime.now())); + // map from [LocationDto] to [LocationSample] + Stream locationSampleStream = locationStream.map( + (location) => LocationSample( + GeoLocation(location.latitude, location.longitude), + DateTime.now())); + // provide the [MobilityFeatures] instance with the LocationSample stream MobilityFeatures().startListening(locationSampleStream); + + // start listening to incoming MobilityContext objects mobilitySubscription = MobilityFeatures().contextStream.listen(onMobilityContext); } - void onData(LocationDto dto) { + /// Called whenever location changes. + void onLocationUpdate(LocationDto dto) { print(dtoToString(dto)); } + /// Called whenever mobility context changes. + void onMobilityContext(MobilityContext context) { + print('Context received: ${context.toJson()}'); + setState(() { + _state = AppState.FEATURES_READY; + _mobilityContext = context; + }); + } + + @override + void dispose() { + mobilitySubscription?.cancel(); + super.dispose(); + } + Widget get featuresOverview { return ListView( children: [ diff --git a/packages/mobility_features/example/pubspec.yaml b/packages/mobility_features/example/pubspec.yaml index 863e892cd..9c766ee87 100644 --- a/packages/mobility_features/example/pubspec.yaml +++ b/packages/mobility_features/example/pubspec.yaml @@ -1,21 +1,7 @@ -name: example -description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. +name: mobility_features_example +description: An example app for the Mobility Features package. publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 +version: 3.1.0 environment: sdk: ">=2.7.0 <3.0.0" @@ -24,59 +10,16 @@ dependencies: flutter: sdk: flutter - geolocator: mobility_features: path: ../ - carp_background_location: - path: ../../carp_background_location - + carp_background_location: ^3.0.1 + # path: ../../carp_background_location - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 dev_dependencies: flutter_test: sdk: flutter -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/mobility_features/lib/mobility_features.dart b/packages/mobility_features/lib/mobility_features.dart index 0e6d4e867..7dc0eea0c 100644 --- a/packages/mobility_features/lib/mobility_features.dart +++ b/packages/mobility_features/lib/mobility_features.dart @@ -14,5 +14,5 @@ part 'package:mobility_features/src/mobility_domain.dart'; part 'package:mobility_features/src/mobility_intermediate.dart'; part 'package:mobility_features/src/mobility_context.dart'; part 'package:mobility_features/src/mobility_serializer.dart'; -part 'package:mobility_features/src/mobility_factory.dart'; +part 'package:mobility_features/src/mobility_features.dart'; part 'package:mobility_features/src/mobility_file_util.dart'; diff --git a/packages/mobility_features/lib/src/mobility_context.dart b/packages/mobility_features/lib/src/mobility_context.dart index 245c0ea67..f8ed408c3 100644 --- a/packages/mobility_features/lib/src/mobility_context.dart +++ b/packages/mobility_features/lib/src/mobility_context.dart @@ -1,20 +1,19 @@ part of mobility_features; /// Daily mobility context. -/// All Stops and Moves should be on the same date. -/// Places are all places for which the duration -/// on the given data is greater than 0 +/// +/// All Stops and Moves are on the same [date]. +/// [places] are all places for which the duration on the given date is greater than 0. class MobilityContext { + late DateTime _timestamp, _date; List _stops; - List? _places, _significantPlaces; + List _places; + List _moves; + late List _significantPlaces; Place? _homePlace; - List _moves; - DateTime? _timestamp, _date; late _HourMatrix _hourMatrix; - /// Features - int? _numberOfPlaces; double? _locationVariance, _entropy, _normalizedEntropy, @@ -23,83 +22,77 @@ class MobilityContext { List? contexts; /// Private constructor, cannot be instantiated from outside - MobilityContext._(this._stops, this._places, this._moves, this._date, - {this.contexts}) { + MobilityContext._( + this._stops, + this._places, + this._moves, + this._date, { + this.contexts, + }) { _timestamp = DateTime.now(); - // If contexts array is null, init to empty array + // if contexts array is null, init to empty array contexts = contexts ?? []; + // compute all the features _significantPlaces = - _places!.where((p) => p.duration > Duration(minutes: 3)).toList(); - - // Compute all the features - _numberOfPlaces = _calculateNumberOfSignificantPlaces(); - - _hourMatrix = _HourMatrix.fromStops(_stops, _places!.length); - + _places.where((p) => p.duration > Duration(minutes: 3)).toList(); + _hourMatrix = _HourMatrix.fromStops(_stops, _places.length); _homePlace = _findHomePlaceToday(); - _homeStay = _calculateHomeStay(); - _locationVariance = _calculateLocationVariance(); - _entropy = _calculateEntropy(); - _normalizedEntropy = _calculateNormalizedEntropy(); - _distanceTravelled = _calculateDistanceTravelled(); } - // Get the date of the context - DateTime? get date => _date; + // The date of this context. + DateTime get date => _date; - /// Get stops today + /// Timestamp at which the features were computed + DateTime get timestamp => _timestamp; + + /// Stops today. List get stops => _stops; - /// Get moves today + /// Moves today. List get moves => _moves; - /// Get places today - List? get places => _places; + /// Places today. + List get places => _places; - /// Get all the significant places (places with a minimum duration) - List? get significantPlaces => _significantPlaces; + /// All significant places, i.e. places with a minimum stay duration. + List get significantPlaces => _significantPlaces; - /// Get the timestamp at which the features were computed - DateTime? get timestamp => _timestamp; + /// Number of significant places visited today. + int get numberOfSignificantPlaces => _significantPlaces.length; - /// Get the home place cluster + /// Home place. + /// Returns null if home cannot be found from the available data. Place? get homePlace => _homePlace; - /// Number of Places today - int? get numberOfSignificantPlaces => _numberOfPlaces; - - /// Home Stay Percentage today - /// A scalar between 0 and 1, i.e. from 0% to 100% + /// Home Stay Percentage today. A scalar between 0 and 1. + /// Returns null if cannot be calculated based on the available data. double? get homeStay => _homeStay; - /// Location Variance today + /// Location variance today. double? get locationVariance => _locationVariance; - /// Entropy - /// High entropy: Time is spent evenly among all places - /// Low entropy: Time is mainly spent at a few of the places + /// Location entropy. + /// + /// * High entropy: Time is spent evenly among all places + /// * Low entropy: Time is mainly spent at a few of the places double? get entropy => _entropy; - /// Normalized entropy, - /// a scalar between 0 and 1 + /// Normalized location entropy. A scalar between 0 and 1. double? get normalizedEntropy => _normalizedEntropy; - /// Distance travelled today, in meters + /// Distance travelled today in meters. double? get distanceTravelled => _distanceTravelled; - /// Private number of places calculation - int _calculateNumberOfSignificantPlaces() => significantPlaces!.length; - /// Private home stay calculation - double _calculateHomeStay() { - if (stops.isEmpty) return -1.0; + double? _calculateHomeStay() { + if (stops.isEmpty) return null; // Latest known sample time DateTime latestTime = _stops.last.departure; @@ -108,10 +101,8 @@ class MobilityContext { int totalTime = latestTime.millisecondsSinceEpoch - latestTime.midnight.millisecondsSinceEpoch; - // Find todays home id, if no home exists today return -1.0 - if (_hourMatrix.homePlaceId == -1) { - return -1.0; - } + // Find todays home id, if no home exists today return null + if (_hourMatrix.homePlaceId == -1) return null; int homeTime = stops .where((s) => s.placeId == _hourMatrix.homePlaceId) @@ -121,20 +112,15 @@ class MobilityContext { return homeTime.toDouble() / totalTime.toDouble(); } - Place? _findHomePlaceToday() { - int home = _hourMatrix.homePlaceId; - if (home == -1) { - return null; - } - return _places!.where((p) => p.id == _hourMatrix.homePlaceId).first; - } + Place? _findHomePlaceToday() => (_hourMatrix.homePlaceId == -1) + ? null + : _places.where((p) => p.id == _hourMatrix.homePlaceId).first; /// Location variance calculation - double _calculateLocationVariance() { + double? _calculateLocationVariance() { // Require at least 2 observations - if (_stops.length < 2) { - return 0.0; - } + if (_stops.length < 2) return 0.0; + double latStd = Stats.fromData(_stops.map((s) => (s.geoLocation.latitude))) .standardDeviation as double; @@ -143,18 +129,16 @@ class MobilityContext { return log(latStd * latStd + lonStd * lonStd + 1); } - double _calculateEntropy() { - // If no places were visited return -1.0 - if (places!.isEmpty) { - return -1.0; - } - // The Entropy is zero when one outcome is certain to occur. - else if (places!.length == 1) { - return 0.0; - } - // Calculate time spent at different places + double? _calculateEntropy() { + // if no places were visited return null + if (places.isEmpty) + return null; + // the Entropy is zero when one outcome is certain to occur + else if (places.length == 1) return 0.0; + + // calculate time spent at different places List durations = - places!.map((p) => p.durationForDate(date)).toList(); + places.map((p) => p.durationForDate(date)).toList(); Duration totalTimeSpent = durations.fold(Duration(), (a, b) => a + b); @@ -167,24 +151,19 @@ class MobilityContext { } /// Private normalized entropy calculation - double _calculateNormalizedEntropy() { - if (places!.length == 1) { - return 0.0; - } - return entropy! / log(places!.length); - } + double _calculateNormalizedEntropy() => + (places.length == 1) ? 0.0 : entropy! / log(places.length); /// Private distance travelled calculation - double _calculateDistanceTravelled() { - return _moves.map((m) => (m.distance)).fold(0.0, (a, b) => a + b!); - } + double _calculateDistanceTravelled() => + _moves.map((m) => (m.distance)).fold(0.0, (a, b) => a + b!); Map toJson() => { - "date": date!.toIso8601String(), - "computed_at": timestamp!.toIso8601String(), + "date": date.toIso8601String(), + "computed_at": timestamp.toIso8601String(), "num_of_stops": stops.length, "num_of_moves": moves.length, - "num_of_significant_places": significantPlaces!.length, + "num_of_significant_places": significantPlaces.length, "normalized_entropy": normalizedEntropy, "home_stay": homeStay, "distance_travelled": distanceTravelled, diff --git a/packages/mobility_features/lib/src/mobility_domain.dart b/packages/mobility_features/lib/src/mobility_domain.dart index 67aff2b99..b48fa1b5d 100644 --- a/packages/mobility_features/lib/src/mobility_domain.dart +++ b/packages/mobility_features/lib/src/mobility_domain.dart @@ -16,7 +16,6 @@ const String _LATITUDE = 'latitude', /// to serialize and deserialize an object abstract class _Serializable { Map toJson(); - _Serializable._fromJson(Map json); } diff --git a/packages/mobility_features/lib/src/mobility_factory.dart b/packages/mobility_features/lib/src/mobility_features.dart similarity index 100% rename from packages/mobility_features/lib/src/mobility_factory.dart rename to packages/mobility_features/lib/src/mobility_features.dart diff --git a/packages/mobility_features/mobility_features_example/.flutter-plugins-dependencies b/packages/mobility_features/mobility_features_example/.flutter-plugins-dependencies deleted file mode 100644 index d2dc30c27..000000000 --- a/packages/mobility_features/mobility_features_example/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"background_locator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/background_locator-1.6.0+1-beta/","dependencies":[]},{"name":"geolocator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/geolocator-6.2.1/","dependencies":[]},{"name":"location_permissions","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/location_permissions-3.0.0+1/","dependencies":[]},{"name":"path_provider","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.28/","dependencies":[]}],"android":[{"name":"background_locator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/background_locator-1.6.0+1-beta/","dependencies":[]},{"name":"geolocator","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/geolocator-6.2.1/","dependencies":[]},{"name":"location_permissions","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/location_permissions-3.0.0+1/","dependencies":[]},{"name":"path_provider","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.28/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.5/","dependencies":[]}],"web":[{"name":"geolocator_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/geolocator_web-1.0.1/","dependencies":[]}]},"dependencyGraph":[{"name":"background_locator","dependencies":[]},{"name":"geolocator","dependencies":["geolocator_web"]},{"name":"geolocator_web","dependencies":[]},{"name":"location_permissions","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2021-06-22 10:23:03.000927","version":"2.2.2"} \ No newline at end of file diff --git a/packages/mobility_features/mobility_features_example/.metadata b/packages/mobility_features/mobility_features_example/.metadata deleted file mode 100644 index ade6bc987..000000000 --- a/packages/mobility_features/mobility_features_example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable - -project_type: app diff --git a/packages/mobility_features/mobility_features_example/README.md b/packages/mobility_features/mobility_features_example/README.md deleted file mode 100644 index 5334c365a..000000000 --- a/packages/mobility_features/mobility_features_example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# mobility_features_example - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/packages/mobility_features/mobility_features_example/android/app/build.gradle b/packages/mobility_features/mobility_features_example/android/app/build.gradle deleted file mode 100644 index 3b4f63431..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 28 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.mobility_features_example" - minSdkVersion 16 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/mobility_features/mobility_features_example/android/app/src/debug/AndroidManifest.xml b/packages/mobility_features/mobility_features_example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 2204da101..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/AndroidManifest.xml b/packages/mobility_features/mobility_features_example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 95b2aa9f3..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/kotlin/com/example/mobility_features_example/MainActivity.kt b/packages/mobility_features/mobility_features_example/android/app/src/main/kotlin/com/example/mobility_features_example/MainActivity.kt deleted file mode 100644 index 2fec82071..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/src/main/kotlin/com/example/mobility_features_example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.mobility_features_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/drawable/launch_background.xml b/packages/mobility_features/mobility_features_example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f88..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7..000000000 Binary files a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79b..000000000 Binary files a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d439148..000000000 Binary files a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34..000000000 Binary files a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eeb..000000000 Binary files a/packages/mobility_features/mobility_features_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/android/app/src/main/res/values/styles.xml b/packages/mobility_features/mobility_features_example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 1f83a33fd..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/android/app/src/profile/AndroidManifest.xml b/packages/mobility_features/mobility_features_example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 2204da101..000000000 --- a/packages/mobility_features/mobility_features_example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/mobility_features/mobility_features_example/android/build.gradle b/packages/mobility_features/mobility_features_example/android/build.gradle deleted file mode 100644 index 3100ad2d5..000000000 --- a/packages/mobility_features/mobility_features_example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/packages/mobility_features/mobility_features_example/android/gradle.properties b/packages/mobility_features/mobility_features_example/android/gradle.properties deleted file mode 100644 index 38c8d4544..000000000 --- a/packages/mobility_features/mobility_features_example/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/mobility_features/mobility_features_example/android/gradle/wrapper/gradle-wrapper.properties b/packages/mobility_features/mobility_features_example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 296b146b7..000000000 --- a/packages/mobility_features/mobility_features_example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/mobility_features/mobility_features_example/android/settings.gradle b/packages/mobility_features/mobility_features_example/android/settings.gradle deleted file mode 100644 index d3b6a4013..000000000 --- a/packages/mobility_features/mobility_features_example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/mobility_features/mobility_features_example/ios/Flutter/AppFrameworkInfo.plist b/packages/mobility_features/mobility_features_example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 6b4c0f78a..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 8.0 - - diff --git a/packages/mobility_features/mobility_features_example/ios/Flutter/Debug.xcconfig b/packages/mobility_features/mobility_features_example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index e8efba114..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/packages/mobility_features/mobility_features_example/ios/Flutter/Release.xcconfig b/packages/mobility_features/mobility_features_example/ios/Flutter/Release.xcconfig deleted file mode 100644 index 399e9340e..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.pbxproj b/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index e87f87d3a..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,586 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - BDC7D7E50844DD8EC1242043 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EF0191EA9C97779E7CB73A3 /* Pods_Runner.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 0EF0191EA9C97779E7CB73A3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 2F7C27A55D006E15F885EE70 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 61830B0A94CB8C7BCC2C8806 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F01B23F0CD7B78D9F46B4E71 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - BDC7D7E50844DD8EC1242043 /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 2E6C788C2D3A0AC7C13D602B /* Pods */ = { - isa = PBXGroup; - children = ( - 61830B0A94CB8C7BCC2C8806 /* Pods-Runner.debug.xcconfig */, - F01B23F0CD7B78D9F46B4E71 /* Pods-Runner.release.xcconfig */, - 2F7C27A55D006E15F885EE70 /* Pods-Runner.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 2E6C788C2D3A0AC7C13D602B /* Pods */, - B70E9F531CE9FBE99B702E55 /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; - B70E9F531CE9FBE99B702E55 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 0EF0191EA9C97779E7CB73A3 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - D8BE761BB894E75EDBF8CFF4 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - EC7A9682D924C7840AB6C52F /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - D8BE761BB894E75EDBF8CFF4 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - EC7A9682D924C7840AB6C52F /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = H33599VJ27; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.mobilityFeatures; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = H33599VJ27; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.mobilityFeatures; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = H33599VJ27; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.mobilityFeatures; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16e..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index a28140cfd..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/AppDelegate.swift b/packages/mobility_features/mobility_features_example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 34cc7fa3b..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,23 +0,0 @@ -import UIKit -import Flutter - -import background_locator - -func registerPlugins(registry: FlutterPluginRegistry) -> () { - if (!registry.hasPlugin("BackgroundLocatorPlugin")) { - GeneratedPluginRegistrant.register(with: registry) - } -} - - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - BackgroundLocatorPlugin.setPluginRegistrantCallback(registerPlugins) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada472..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf030..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0b..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7e..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e1..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/mobility_features/mobility_features_example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Base.lproj/Main.storyboard b/packages/mobility_features/mobility_features_example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516f..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Info.plist b/packages/mobility_features/mobility_features_example/ios/Runner/Info.plist deleted file mode 100644 index cb13bd74d..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Info.plist +++ /dev/null @@ -1,55 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - mobility_features_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - NSLocationAlwaysAndWhenInUseUsageDescription - This app needs access to location when open and in the background. - NSLocationAlwaysUsageDescription - This app needs access to location when in the background. - NSLocationWhenInUseUsageDescription - This app needs access to location when open. - UIBackgroundModes - - location - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/packages/mobility_features/mobility_features_example/ios/Runner/Runner-Bridging-Header.h b/packages/mobility_features/mobility_features_example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a560..000000000 --- a/packages/mobility_features/mobility_features_example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/packages/mobility_features/mobility_features_example/lib/main.dart b/packages/mobility_features/mobility_features_example/lib/main.dart deleted file mode 100644 index 7d6a98e77..000000000 --- a/packages/mobility_features/mobility_features_example/lib/main.dart +++ /dev/null @@ -1,273 +0,0 @@ -library mobility_app; - -import 'dart:async'; -import 'package:flutter/material.dart'; - -import 'package:carp_background_location/carp_background_location.dart'; -import 'package:mobility_features/mobility_features.dart'; - -part 'stops_page.dart'; - -part 'moves_page.dart'; - -part 'places_page.dart'; - -void main() => runApp(MyApp()); - -Widget entry(String key, String value, Icon icon) { - return Container( - padding: const EdgeInsets.all(2), - margin: EdgeInsets.all(3), - child: ListTile( - leading: icon, - title: Text(key), - trailing: Text(value), - )); -} - -String formatDate(DateTime date) { - return '${date.year}/${date.month}/${date.day}'; -} - -String interval(DateTime a, DateTime b) { - String pad(int x) => '${x.toString().padLeft(2, '0')}'; - return '${pad(a.hour)}:${pad(a.minute)}:${pad(a.second)} - ${pad(b.hour)}:${pad(b.minute)}:${pad(b.second)}'; -} - -String formatDuration(Duration duration) { - String twoDigits(int n) => n.toString().padLeft(2, "0"); - String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); - String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); - return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds"; -} - -final stopIcon = Icon(Icons.my_location); -final moveIcon = Icon(Icons.directions_walk); -final placeIcon = Icon(Icons.place); -final featuresIcon = Icon(Icons.assessment); -final homeStayIcon = Icon(Icons.home); -final distanceTravelledIcon = Icon(Icons.card_travel); -final entropyIcon = Icon(Icons.equalizer); -final varianceIcon = Icon(Icons.swap_calls); - -enum AppState { NO_FEATURES, CALCULATING_FEATURES, FEATURES_READY } - -class MyApp extends StatelessWidget { - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: MyHomePage(title: 'Mobility Features Demo'), - ); - } -} - -String dtoToString(LocationDto dto) => - '${dto.latitude}, ${dto.longitude} @ ${DateTime.fromMillisecondsSinceEpoch(dto.time ~/ 1)}'; - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - AppState _state = AppState.NO_FEATURES; - - int _currentIndex = 0; - - /// Location Streaming - LocationManager locationManager = LocationManager.instance; - Stream dtoStream; - StreamSubscription dtoSubscription; - - /// Mobility Features stream - StreamSubscription mobilitySubscription; - MobilityContext _mobilityContext; - - @override - void initState() { - super.initState(); - - /// Set up Mobility Features - MobilityFeatures().stopDuration = Duration(seconds: 20); - MobilityFeatures().placeRadius = 50.0; - MobilityFeatures().stopRadius = 5.0; - - /// Setup Location Manager - locationManager.distanceFilter = 0; - locationManager.interval = 1; - locationManager.notificationTitle = 'Mobility Features'; - locationManager.notificationMsg = 'Your geo-location is being tracked'; - streamInit(); - } - - @override - void dispose() { - mobilitySubscription?.cancel(); - super.dispose(); - } - - void onMobilityContext(MobilityContext context) { - print('Context received: ${context.toJson()}'); - setState(() { - _state = AppState.FEATURES_READY; - _mobilityContext = context; - }); - } - - void streamInit() async { - /// Set up streams: - /// * Subscribe to stream in case it is already running (Android only) - /// * Subscribe to MobilityContext updates - dtoStream = locationManager.dtoStream; - dtoSubscription = dtoStream.listen(onData); - - // Subscribe if it hasn't been done already - if (dtoSubscription != null) { - dtoSubscription.cancel(); - } - dtoSubscription = dtoStream.listen(onData); - await locationManager.start(); - - Stream locationSampleStream = dtoStream.map((e) => - LocationSample(GeoLocation(e.latitude, e.longitude), DateTime.now())); - - MobilityFeatures().startListening(locationSampleStream); - mobilitySubscription = - MobilityFeatures().contextStream.listen(onMobilityContext); - } - - void onData(LocationDto dto) { - print(dtoToString(dto)); - } - - Widget get featuresOverview { - return ListView( - children: [ - entry("Stops", "${_mobilityContext.stops.length}", stopIcon), - entry("Moves", "${_mobilityContext.moves.length}", moveIcon), - entry("Significant Places", - "${_mobilityContext.numberOfSignificantPlaces}", placeIcon), - entry( - "Home Stay", - _mobilityContext.homeStay < 0 - ? "?" - : "${(_mobilityContext.homeStay * 100).toStringAsFixed(1)}%", - homeStayIcon), - entry( - "Distance Travelled", - "${(_mobilityContext.distanceTravelled / 1000).toStringAsFixed(2)} km", - distanceTravelledIcon), - entry( - "Normalized Entropy", - "${_mobilityContext.normalizedEntropy.toStringAsFixed(2)}", - entropyIcon), - entry( - "Location Variance", - "${(111.133 * _mobilityContext.locationVariance).toStringAsFixed(5)} km", - varianceIcon), - ], - ); - } - - List get contentNoFeatures { - return [ - Container( - margin: EdgeInsets.all(25), - child: Text( - 'Move around to start generating features', - style: TextStyle(fontSize: 20), - )) - ]; - } - - List get contentFeaturesReady { - return [ - Container( - margin: EdgeInsets.all(25), - child: Column(children: [ - Text( - 'Statistics for today,', - style: TextStyle(fontSize: 20), - ), - Text( - '${formatDate(_mobilityContext.date)}', - style: TextStyle(fontSize: 20, color: Colors.blue), - ), - ])), - Expanded(child: featuresOverview), - ]; - } - - Widget get content { - List children; - if (_state == AppState.FEATURES_READY) - children = contentFeaturesReady; - else - children = contentNoFeatures; - return Column(children: children); - } - - void onTabTapped(int index) { - setState(() { - _currentIndex = index; - }); - } - - Widget _navBar() { - return BottomNavigationBar( - onTap: onTabTapped, // new - currentIndex: _currentIndex, // this will be set when a new tab is tapped - type: BottomNavigationBarType.fixed, - items: [ - BottomNavigationBarItem(icon: featuresIcon, label: 'Features'), - BottomNavigationBarItem(icon: stopIcon, label: 'Stops'), - BottomNavigationBarItem(icon: placeIcon, label: 'Places'), - BottomNavigationBarItem(icon: moveIcon, label: 'Moves') - ], - ); - } - - @override - Widget build(BuildContext context) { - List stops = []; - List moves = []; - List places = []; - - if (_mobilityContext != null) { - for (var x in _mobilityContext.stops) print(x); - for (var x in _mobilityContext.moves) { - print(x); - print('${x.stopFrom} --> ${x.stopTo}'); - } - stops = _mobilityContext.stops; - moves = _mobilityContext.moves; - places = _mobilityContext.places; - } - - List pages = [ - content, - StopsPage(stops), - PlacesPage(places), - MovesPage(moves), - ]; - - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.teal, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: pages[_currentIndex], - bottomNavigationBar: _navBar(), - ); - } -} diff --git a/packages/mobility_features/mobility_features_example/lib/moves_page.dart b/packages/mobility_features/mobility_features_example/lib/moves_page.dart deleted file mode 100644 index b6486a73a..000000000 --- a/packages/mobility_features/mobility_features_example/lib/moves_page.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of mobility_app; - -class MovesPage extends StatelessWidget { - final List moves; - - MovesPage(this.moves); - - Widget moveEntry(Move m) { - return Container( - padding: const EdgeInsets.all(2), - margin: EdgeInsets.all(3), - child: ListTile( - leading: Text('Place ${m.stopFrom.placeId} → ${m.stopTo.placeId}'), - title: Text('${m.distance.toInt()} meters'), - trailing: Text('${formatDuration(m.duration)}'), - )); - } - - Widget list() { - return ListView.builder( - itemCount: moves.length, - itemBuilder: (ctx, index) => moveEntry(moves[index])); - } - - @override - Widget build(BuildContext context) { - return Container( - child: list(), - ); - } -} diff --git a/packages/mobility_features/mobility_features_example/lib/places_page.dart b/packages/mobility_features/mobility_features_example/lib/places_page.dart deleted file mode 100644 index 1232f75af..000000000 --- a/packages/mobility_features/mobility_features_example/lib/places_page.dart +++ /dev/null @@ -1,34 +0,0 @@ -part of mobility_app; - -class PlacesPage extends StatelessWidget { - final List places; - - PlacesPage(this.places); - - Widget placeEntry(Place p) { - String lat = p.geoLocation.latitude.toStringAsFixed(4); - String lon = p.geoLocation.longitude.toStringAsFixed(4); - - return Container( - padding: const EdgeInsets.all(2), - margin: EdgeInsets.all(3), - child: ListTile( - leading: Text('Place ID ${p.id}'), - title: Text('$lat, $lon'), - trailing: Text('${formatDuration(p.duration)}'), - )); - } - - Widget list() { - return ListView.builder( - itemCount: places.length, - itemBuilder: (ctx, index) => placeEntry(places[index])); - } - - @override - Widget build(BuildContext context) { - return Container( - child: list(), - ); - } -} diff --git a/packages/mobility_features/mobility_features_example/lib/stops_page.dart b/packages/mobility_features/mobility_features_example/lib/stops_page.dart deleted file mode 100644 index 66e6bb4fd..000000000 --- a/packages/mobility_features/mobility_features_example/lib/stops_page.dart +++ /dev/null @@ -1,33 +0,0 @@ -part of mobility_app; - -class StopsPage extends StatelessWidget { - final List stops; - - StopsPage(this.stops); - - Widget stopEntry(Stop s) { - String lat = s.geoLocation.latitude.toStringAsFixed(4); - String lon = s.geoLocation.longitude.toStringAsFixed(4); - return Container( - padding: const EdgeInsets.all(2), - margin: EdgeInsets.all(3), - child: ListTile( - leading: Text('Place ${s.placeId}'), - title: Text('${interval(s.arrival, s.departure)}'), - trailing: Text('$lat, $lon'), - )); - } - - Widget list() { - return ListView.builder( - itemCount: stops.length, - itemBuilder: (ctx, index) => stopEntry(stops[index])); - } - - @override - Widget build(BuildContext context) { - return Container( - child: list(), - ); - } -} diff --git a/packages/mobility_features/mobility_features_example/pubspec.yaml b/packages/mobility_features/mobility_features_example/pubspec.yaml deleted file mode 100644 index 832b4121f..000000000 --- a/packages/mobility_features/mobility_features_example/pubspec.yaml +++ /dev/null @@ -1,80 +0,0 @@ -name: mobility_features_example -description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -environment: - sdk: ">=2.7.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - geolocator: - mobility_features: - path: ../ - carp_background_location: ^1.0.3 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/mobility_features/pubspec.yaml b/packages/mobility_features/pubspec.yaml index 880fb08b3..030c43ccb 100644 --- a/packages/mobility_features/pubspec.yaml +++ b/packages/mobility_features/pubspec.yaml @@ -1,6 +1,6 @@ name: mobility_features description: Calculation of real-time mobility features like places, stops, and home stay -version: 3.0.0+2 +version: 3.1.0 homepage: https://github.com/cph-cachet/flutter-plugins/ environment: diff --git a/packages/mobility_features/test/mobility_features_test.dart b/packages/mobility_features/test/mobility_features_test.dart index ce84600da..df215cbf3 100644 --- a/packages/mobility_features/test/mobility_features_test.dart +++ b/packages/mobility_features/test/mobility_features_test.dart @@ -413,9 +413,9 @@ void main() async { print("STOPS"); printList(event.stops); print("ALL PLACES"); - printList(event.places!); + printList(event.places); print("SIGNIFICANT PLACES"); - printList(event.significantPlaces!); + printList(event.significantPlaces); print( "TOTAL DURATION (STOPS): ${event.stops.map((p) => p.duration).reduce((a, b) => a + b)}"); }, count: 283)); diff --git a/packages/mobility_features/test/testdata/location_samples.json b/packages/mobility_features/test/testdata/location_samples.json index 9ba3251e6..3f290383b 100644 --- a/packages/mobility_features/test/testdata/location_samples.json +++ b/packages/mobility_features/test/testdata/location_samples.json @@ -68,3 +68,23 @@ {"geo_location":{"latitude":48.17132510439827,"longitude":11.563184492969626},"datetime":"1581633592554"} {"geo_location":{"latitude":48.171309745958155,"longitude":11.563212691472879},"datetime":"1581633598543"} {"geo_location":{"latitude":48.17130980868023,"longitude":11.563223221990256},"datetime":"1581633604549"} +{"geo_location":{"latitude":48.17130116552479,"longitude":11.563204770731598},"datetime":"1581633610547"} +{"geo_location":{"latitude":48.17132024048344,"longitude":11.563197098288784},"datetime":"1581633616553"} +{"geo_location":{"latitude":48.171299319142854,"longitude":11.563184923043417},"datetime":"1581633622547"} +{"geo_location":{"latitude":48.171310790416754,"longitude":11.563198310967506},"datetime":"1581633628544"} +{"geo_location":{"latitude":48.17131589082105,"longitude":11.563213775430425},"datetime":"1581633634615"} +{"geo_location":{"latitude":48.17131124461477,"longitude":11.5632183996362},"datetime":"1581633640550"} +{"geo_location":{"latitude":48.17132126286372,"longitude":11.563221820856478},"datetime":"1581633646551"} +{"geo_location":{"latitude":48.17130773221799,"longitude":11.56319857289543},"datetime":"1581633652555"} +{"geo_location":{"latitude":48.17133161288919,"longitude":11.563206664336509},"datetime":"1581633658545"} +{"geo_location":{"latitude":48.17132725995403,"longitude":11.563204133995162},"datetime":"1581633664545"} +{"geo_location":{"latitude":48.17131174010998,"longitude":11.563214543661774},"datetime":"1581633670547"} +{"geo_location":{"latitude":48.17131006002293,"longitude":11.563231760120583},"datetime":"1581633676549"} +{"geo_location":{"latitude":48.17131857591096,"longitude":11.563203957262328},"datetime":"1581633682549"} +{"geo_location":{"latitude":48.1713001793906,"longitude":11.563207264402903},"datetime":"1581633688547"} +{"geo_location":{"latitude":48.171320995827394,"longitude":11.56320571687284},"datetime":"1581633694551"} +{"geo_location":{"latitude":48.171289834325194,"longitude":11.563235278192037},"datetime":"1581633700537"} +{"geo_location":{"latitude":48.171305118409826,"longitude":11.563221094539754},"datetime":"1581633706547"} +{"geo_location":{"latitude":48.171306756445375,"longitude":11.563214587831721},"datetime":"1581633712549"} +{"geo_location":{"latitude":48.17129139009002,"longitude":11.563199191004612},"datetime":"1581633718547"} +{"geo_location":{"latitude":48.17131649786042,"longitude":11.56319249730382},"datetime":"1581633724548"}