├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── TelematicsSDKPlugin.h │ ├── FlutterPluginCode.swift │ └── TelematicsSDKPlugin.m ├── .swiftformat ├── .gitignore ├── telematics_sdk.podspec └── swiftlint.yml ├── android ├── .idea │ ├── .name │ ├── compiler.xml │ ├── kotlinc.xml │ ├── vcs.xml │ ├── migrations.xml │ ├── misc.xml │ └── gradle.xml ├── settings.gradle ├── gradle.properties ├── .gitignore ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── telematicssdk │ │ │ ├── TagsExtentions.kt │ │ │ ├── LocationListenerImp.kt │ │ │ ├── TelematicsSDKApp.kt │ │ │ ├── TagsProcessingListenerImp.kt │ │ │ └── TelematicsSDKPlugin.kt │ │ └── AndroidManifest.xml └── build.gradle ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ ├── Info.plist │ │ └── London route 2.gpx │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ └── Podfile ├── android │ ├── app │ │ ├── proguard-files.pro │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── telematicssdk │ │ │ │ │ │ └── example │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ └── App.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── build.gradle │ └── settings.gradle ├── README.md ├── .metadata ├── pubspec.yaml ├── lib │ ├── main.dart │ └── title_screen.dart └── .gitignore ├── .vscode ├── settings.json └── launch.json ├── lib ├── src │ ├── data │ │ ├── api_language.dart │ │ ├── accident_detection_sensitivity.dart │ │ ├── permission_wizard_result.dart │ │ ├── tag.dart │ │ ├── track_location.dart │ │ ├── future_track_callbacks.dart │ │ ├── track_tag.dart │ │ ├── status.dart │ │ ├── delegates_callbacks.dart │ │ └── track_processed.dart │ ├── native_call_handler.dart │ └── tracking_api.dart └── telematics_sdk.dart ├── .markdownlint.json ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml └── inspectionProfiles │ ├── profiles_settings.xml │ └── ktlint.xml ├── .gitignore ├── .metadata ├── pubspec.yaml ├── CHANGELOG.md ├── test └── native_call_handler_test.dart ├── README.md └── LICENSE /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/.idea/.name: -------------------------------------------------------------------------------- 1 | telematics_sdk -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'telematics_sdk' 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "telematics" 4 | ] 5 | } -------------------------------------------------------------------------------- /example/android/app/proguard-files.pro: -------------------------------------------------------------------------------- 1 | -keep public class com.telematicssdk.tracking.** {*;} -------------------------------------------------------------------------------- /lib/src/data/api_language.dart: -------------------------------------------------------------------------------- 1 | enum ApiLanguage { none, english, russian, portuguese, spanish } 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # telematics_sdk_example 2 | 3 | Demonstrates how to use the telematics_sdk plugin. 4 | -------------------------------------------------------------------------------- /lib/src/data/accident_detection_sensitivity.dart: -------------------------------------------------------------------------------- 1 | enum AccidentDetectionSensitivity { normal, sensitive, tough } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Classes/TelematicsSDKPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface TelematicsSDKPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Classes/FlutterPluginCode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum FlutterPluginCode { 4 | static let failure = "TELEMATICS_SDK_FAILURE" 5 | } 6 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "//MD033": "inline HTML tags warning", 4 | "MD033": false, 5 | "//MD013": "line-length > 80 warning", 6 | "MD013": false 7 | } -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .idea/* 5 | !.idea/codeStyles 6 | !.idea/inspectionProfiles 7 | *.iml 8 | 9 | .packages 10 | .pub/ 11 | 12 | build/ 13 | 14 | Podfile.lock 15 | pubspec.lock 16 | -------------------------------------------------------------------------------- /android/.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/telematicssdk/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/.swiftformat: -------------------------------------------------------------------------------- 1 | # options 2 | --self init-only # redundantSelf 3 | --importgrouping testable-bottom # sortedImports 4 | --trimwhitespace always # trailingSpace 5 | 6 | # rules 7 | --rules redundantParens,redundantSelf,sortedImports,trailingSpace 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 26 11:15:05 MSK 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /android/.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Classes/TelematicsSDKPlugin.m: -------------------------------------------------------------------------------- 1 | #import "TelematicsSDKPlugin.h" 2 | #import 3 | 4 | @implementation TelematicsSDKPlugin 5 | + (void)registerWithRegistrar:(NSObject*)registrar { 6 | [SwiftTelematicsSDKPlugin registerWithRegistrar:registrar]; 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/data/permission_wizard_result.dart: -------------------------------------------------------------------------------- 1 | enum PermissionWizardResult { 2 | /// User finished wizard with all required permissions granted 3 | allGranted, 4 | 5 | /// User canceled wizard 6 | canceled, 7 | 8 | /// User finished wizard with not all required permissions granted 9 | notAllGranted, 10 | } 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: unknown 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: unknown 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: telematics_sdk_example 2 | description: Demonstrates how to use the telematics_sdk plugin. 3 | publish_to: none 4 | version: 0.0.2+15 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | telematics_sdk: 14 | path: ../ 15 | uuid: ^4.3.2 16 | 17 | flutter: 18 | uses-material-design: true 19 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /.idea/inspectionProfiles/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/data/tag.dart: -------------------------------------------------------------------------------- 1 | class Tag { 2 | final String source; 3 | final String tag; 4 | 5 | const Tag({ 6 | required this.source, 7 | required this.tag, 8 | }); 9 | 10 | factory Tag.fromJson(Map json) { 11 | final source = json['source'] as String; 12 | final tag = json['tag'] as String; 13 | 14 | return Tag( 15 | source: source, 16 | tag: tag, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "telematics_sdk_example", 9 | "cwd": "example", 10 | "request": "launch", 11 | "type": "dart", 12 | }, 13 | ] 14 | } -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:telematics_sdk_example/title_screen.dart'; 3 | 4 | void main() { 5 | WidgetsFlutterBinding.ensureInitialized(); 6 | runApp(const MyApp()); 7 | } 8 | 9 | class MyApp extends StatelessWidget { 10 | const MyApp({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | title: 'TelematicsSDK Example', 16 | home: TitleScreen(), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/telematics_sdk.dart: -------------------------------------------------------------------------------- 1 | export 'package:telematics_sdk/src/tracking_api.dart'; 2 | export 'package:telematics_sdk/src/data/permission_wizard_result.dart'; 3 | export 'package:telematics_sdk/src/data/status.dart'; 4 | export 'package:telematics_sdk/src/data/track_tag.dart'; 5 | export 'package:telematics_sdk/src/data/tag.dart'; 6 | export 'package:telematics_sdk/src/data/track_location.dart'; 7 | export 'package:telematics_sdk/src/data/api_language.dart'; 8 | export 'package:telematics_sdk/src/data/accident_detection_sensitivity.dart'; -------------------------------------------------------------------------------- /lib/src/data/track_location.dart: -------------------------------------------------------------------------------- 1 | class TrackLocation { 2 | final double latitude; 3 | final double longitude; 4 | 5 | const TrackLocation({ 6 | required this.latitude, 7 | required this.longitude, 8 | }); 9 | 10 | factory TrackLocation.fromJson(Map json) { 11 | final latitude = json['latitude'] as double; 12 | final longitude = json['longitude'] as double; 13 | 14 | return TrackLocation( 15 | latitude: latitude, 16 | longitude: longitude, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import TelematicsSDK 4 | 5 | @main 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | RPEntry.initializeSDK() 12 | RPEntry.instance.application(application, didFinishLaunchingWithOptions: launchOptions) 13 | GeneratedPluginRegistrant.register(with: self) 14 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/data/future_track_callbacks.dart: -------------------------------------------------------------------------------- 1 | import 'package:telematics_sdk/src/data/status.dart'; 2 | import 'package:telematics_sdk/src/data/tag.dart'; 3 | 4 | typedef OnTagAddCallback = void Function( 5 | Status status, 6 | Tag tag, 7 | int activationTime, 8 | ); 9 | 10 | typedef OnTagRemoveCallback = void Function( 11 | Status status, 12 | Tag tag, 13 | int deactivationTime, 14 | ); 15 | 16 | typedef OnAllTagsRemoveCallback = void Function( 17 | Status status, 18 | int deactivatedTagsCount, 19 | int time, 20 | ); 21 | 22 | typedef OnGetTagsCallback = void Function( 23 | Status status, 24 | List? tags, 25 | int time, 26 | ); 27 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: telematics_sdk 2 | version: 0.3.4 3 | description: A Flutter plugin for integration Telematics SDK in Android and iOS applications. 4 | homepage: https://docs.telematicssdk.com 5 | repository: https://github.com/Mobile-Telematics/telematicsSDK-demoapp-flutter- 6 | issue_tracker: https://github.com/Mobile-Telematics/telematicsSDK-demoapp-flutter-/issues 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | environment: 17 | sdk: ">=3.0.0 <4.0.0" 18 | flutter: ">=3.16.0" 19 | 20 | flutter: 21 | plugin: 22 | platforms: 23 | android: 24 | package: com.telematicssdk 25 | pluginClass: TelematicsSDKPlugin 26 | ios: 27 | pluginClass: TelematicsSDKPlugin 28 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/telematicssdk/TagsExtentions.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk 2 | 3 | import org.json.JSONObject 4 | import com.telematicssdk.tracking.server.model.sdk.TrackTag 5 | import com.telematicssdk.tracking.server.model.sdk.raw_tags.Tag 6 | 7 | fun TrackTag.toJsonString(): String { 8 | val json = mapOf( 9 | "source" to source, 10 | "tag" to tag, 11 | "type" to type, 12 | ) 13 | 14 | val jsonObject = JSONObject(json) 15 | 16 | return jsonObject.toString() 17 | } 18 | 19 | fun Tag.toJsonString(): String { 20 | val json = mapOf( 21 | "source" to source, 22 | "tag" to tag, 23 | ) 24 | 25 | val jsonObject = JSONObject(json) 26 | 27 | return jsonObject.toString() 28 | } 29 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /lib/src/data/track_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | @immutable 4 | class TrackTag { 5 | final String? source; 6 | final String tag; 7 | final String? type; 8 | 9 | const TrackTag({ 10 | required this.source, 11 | required this.tag, 12 | required this.type, 13 | }); 14 | 15 | factory TrackTag.fromJson(Map json) { 16 | final source = json['source'] as String?; 17 | final tag = json['tag'] as String; 18 | final type = json['type'] as String?; 19 | 20 | return TrackTag( 21 | source: source, 22 | tag: tag, 23 | type: type, 24 | ); 25 | } 26 | 27 | Map toJson() => { 28 | if (source != null) 'source': source!, 29 | 'tag': tag, 30 | if (type != null) 'type': type!, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.12.0" apply false 22 | id "org.jetbrains.kotlin.android" version "2.1.10" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 13.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/telematicssdk/LocationListenerImp.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk 2 | 3 | import android.location.Location 4 | import android.os.Looper 5 | import android.os.Handler 6 | import io.flutter.plugin.common.MethodChannel 7 | import com.telematicssdk.tracking.LocationListener 8 | 9 | class LocationListenerImp(private val channel: MethodChannel): LocationListener { 10 | 11 | override fun onLocationChanged(location: Location?) { 12 | location?.let { 13 | val latitude = it.latitude 14 | val longitude = it.longitude 15 | val json = mapOf( 16 | "latitude" to latitude, 17 | "longitude" to longitude 18 | ) 19 | Handler(Looper.getMainLooper()).post { 20 | channel.invokeMethod("onLocationChanged", json) 21 | } 22 | } ?: run { 23 | println("Location is null") 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /ios/telematics_sdk.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint telematics_sdk.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'telematics_sdk' 7 | s.version = '0.3.4' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://damoov.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Damoov Telematics' => 'admin@damoov.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '12.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | 24 | # Dependency 25 | s.dependency 'TelematicsSDK', '~> 7.0.2' 26 | end 27 | -------------------------------------------------------------------------------- /lib/src/data/status.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | @immutable 4 | class Status { 5 | static const Status success = Status._('SUCCESS'); 6 | static const Status offline = Status._('OFFLINE'); 7 | static const Status errorTagOperation = Status._('ERROR_TAG_OPERATION'); 8 | static const Status errorInvalidTagSpecified = 9 | Status._('ERROR_INVALID_TAG_SPECIFIED'); 10 | static const Status errorWrongTime = Status._('ERROR_WRONG_TIME'); 11 | static const Status someError = Status._('SOME_ERROR'); 12 | 13 | static const _values = { 14 | 'SUCCESS': Status.success, 15 | 'OFFLINE': Status.offline, 16 | 'ERROR_TAG_OPERATION': Status.errorTagOperation, 17 | 'ERROR_INVALID_TAG_SPECIFIED': Status.errorInvalidTagSpecified, 18 | 'ERROR_WRONG_TIME': Status.errorWrongTime, 19 | }; 20 | 21 | final String _value; 22 | 23 | const Status._(this._value); 24 | 25 | factory Status.fromString(String str) { 26 | return _values[str] ?? someError; 27 | } 28 | 29 | @override 30 | String toString() => _value; 31 | } 32 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/telematicssdk/example/App.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk.example 2 | 3 | import android.util.Log 4 | import com.telematicssdk.TelematicsSDKApp 5 | import com.telematicssdk.tracking.Settings 6 | import com.telematicssdk.tracking.TrackingApi 7 | 8 | class App : TelematicsSDKApp() { 9 | 10 | override fun onCreate() { 11 | val api = TrackingApi.getInstance() 12 | api.initialize(this, setTelematicsSettings()) 13 | Log.d("App.onCreate", "TrackingApi initialized") 14 | Log.d("App.onCreate", "created") 15 | super.onCreate() 16 | } 17 | 18 | override fun setTelematicsSettings(): Settings { 19 | val settings = Settings( 20 | stopTrackingTimeout = Settings.stopTrackingTimeHigh, 21 | accuracy = Settings.accuracyHigh, 22 | autoStartOn = true, 23 | elmOn = false, 24 | hfOn = true 25 | ) 26 | Log.d("App.setTelematicsSettings", "setTelematicsSettings") 27 | return settings 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/src/data/delegates_callbacks.dart: -------------------------------------------------------------------------------- 1 | class HeartbeatSentResult { 2 | final bool state; 3 | final bool success; 4 | 5 | const HeartbeatSentResult({ 6 | required this.state, 7 | required this.success, 8 | }); 9 | 10 | factory HeartbeatSentResult.fromJson(Map json) { 11 | final state = json['state'] as bool; 12 | final success = json['success'] as bool; 13 | 14 | return HeartbeatSentResult( 15 | state: state, 16 | success: success, 17 | ); 18 | } 19 | } 20 | 21 | class SpeedLimitNotificationResult { 22 | final double speedLimit; 23 | final double speed; 24 | final double latitude; 25 | final double longitude; 26 | final String date; 27 | 28 | const SpeedLimitNotificationResult( 29 | {required this.speedLimit, 30 | required this.speed, 31 | required this.latitude, 32 | required this.longitude, 33 | required this.date}); 34 | 35 | factory SpeedLimitNotificationResult.fromJson(Map json) { 36 | final speedLimit = json['speedLimit'] as double; 37 | final speed = json['speed'] as double; 38 | final latitude = json['latitude'] as double; 39 | final longitude = json['longitude'] as double; 40 | final date = json['date'] as String; 41 | 42 | return SpeedLimitNotificationResult( 43 | speedLimit: speedLimit, 44 | speed: speed, 45 | latitude: latitude, 46 | longitude: longitude, 47 | date: date); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/telematicssdk/TelematicsSDKApp.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk 2 | 3 | import android.util.Log 4 | import androidx.annotation.CallSuper 5 | import com.telematicssdk.tracking.Settings 6 | import com.telematicssdk.tracking.TrackingApi 7 | import io.flutter.app.FlutterApplication 8 | 9 | open class TelematicsSDKApp : FlutterApplication() { 10 | 11 | @CallSuper 12 | override fun onCreate() { 13 | super.onCreate() 14 | Log.d("TelematicsSDKApp.onCreate", "created") 15 | 16 | val api = TrackingApi.getInstance() 17 | if (!api.isInitialized()) { 18 | api.initialize(this, setTelematicsSettings()) 19 | Log.d("TelematicsSDKApp.onCreate", "TrackingApi initialized") 20 | } 21 | } 22 | 23 | /** 24 | * Default Setting constructor 25 | * Stop tracking time is 5 minute. 26 | * Parking radius is 100 meters. 27 | * Auto start tracking is true. 28 | * hfOn - true if HIGH FREQUENCY data recording from sensors (acc, gyro) is ON and false otherwise. 29 | * isElmOn - true if data recording from ELM327 devices is ON and false otherwise. 30 | */ 31 | open fun setTelematicsSettings(): Settings { 32 | val settings = Settings( 33 | stopTrackingTimeout = Settings.stopTrackingTimeHigh, 34 | accuracy = Settings.accuracyHigh, 35 | autoStartOn = true, 36 | elmOn = false, 37 | hfOn = true, 38 | ) 39 | Log.d("TelematicsSDKApp", "setTelematicsSettings") 40 | return settings 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | # Uncomment this line to define a global platform for your project 4 | platform :ios, '13.0' 5 | 6 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 7 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 8 | 9 | project 'Runner', { 10 | 'Debug' => :debug, 11 | 'Profile' => :release, 12 | 'Release' => :release, 13 | } 14 | 15 | def flutter_root 16 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 17 | unless File.exist?(generated_xcode_build_settings_path) 18 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 19 | end 20 | 21 | File.foreach(generated_xcode_build_settings_path) do |line| 22 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 23 | return matches[1].strip if matches 24 | end 25 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 26 | end 27 | 28 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 29 | 30 | flutter_ios_podfile_setup 31 | 32 | target 'Runner' do 33 | use_frameworks! 34 | use_modular_headers! 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | target.build_configurations.each do |config| 43 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.telematicssdk' 2 | version '2.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '2.1.10' 6 | ext.raxel_tracking = '3.1.1' 7 | repositories { 8 | google() 9 | jcenter() 10 | } 11 | 12 | dependencies { 13 | //noinspection GradleDependency 14 | classpath 'com.android.tools.build:gradle:8.12.0' 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 16 | } 17 | } 18 | 19 | rootProject.allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { 24 | url "https://s3.us-east-2.amazonaws.com/android.telematics.sdk.production/" 25 | } 26 | } 27 | } 28 | 29 | apply plugin: 'com.android.library' 30 | apply plugin: 'kotlin-android' 31 | 32 | android { 33 | compileSdkVersion 35 34 | namespace "com.telematicssdk" 35 | sourceSets { 36 | main.java.srcDirs += 'src/main/kotlin' 37 | } 38 | defaultConfig { 39 | minSdkVersion 24 40 | // javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true 41 | } 42 | lintOptions { 43 | disable 'InvalidPackage' 44 | } 45 | 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_17 48 | targetCompatibility JavaVersion.VERSION_17 49 | } 50 | 51 | kotlinOptions { 52 | jvmTarget = "17" 53 | } 54 | 55 | packagingOptions { 56 | exclude 'META-INF/INDEX.LIST' 57 | exclude 'META-INF/io.netty.versions.properties' 58 | } 59 | } 60 | 61 | dependencies { 62 | //noinspection GradleDependency 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | implementation "com.telematicssdk:tracking:$raxel_tracking" 65 | } 66 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/telematicssdk/TagsProcessingListenerImp.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk 2 | 3 | import com.telematicssdk.tracking.TagsProcessingListener 4 | import com.telematicssdk.tracking.model.database.models.raw_tags.Status 5 | import com.telematicssdk.tracking.server.model.sdk.raw_tags.Tag 6 | import io.flutter.plugin.common.MethodChannel 7 | 8 | class TagsProcessingListenerImp(private val channel: MethodChannel) : TagsProcessingListener { 9 | override fun onTagAdd(status: Status, tag: Tag, activationTime: Long) { 10 | val json = mapOf( 11 | "status" to status.name, 12 | "tag" to tag.toJsonString(), 13 | "activationTime" to activationTime 14 | ) 15 | 16 | channel.invokeMethod("onTagAdd", json) 17 | } 18 | 19 | override fun onTagRemove(status: Status, tag: Tag, deactivationTime: Long) { 20 | val json = mapOf( 21 | "status" to status.name, 22 | "tag" to tag.toJsonString(), 23 | "deactivationTime" to deactivationTime 24 | ) 25 | 26 | channel.invokeMethod("onTagRemove", json) 27 | } 28 | 29 | override fun onAllTagsRemove(status: Status, deactivatedTagsCount: Int, time: Long) { 30 | val json = mapOf( 31 | "status" to status.name, 32 | "deactivatedTagsCount" to deactivatedTagsCount, 33 | "time" to time 34 | ) 35 | 36 | channel.invokeMethod("onAllTagsRemove", json) 37 | } 38 | 39 | override fun onGetTags(status: Status, tags: Array?, time: Long) { 40 | val json = mapOf( 41 | "status" to status.name, 42 | "tags" to tags?.map { it.toJsonString() }, 43 | "time" to time 44 | ) 45 | 46 | channel.invokeMethod("onGetTags", json) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.4 4 | 5 | * Updated iOS sdk version to 7.0.2 6 | 7 | ## 0.3.3 8 | 9 | * Updated Gradle version to 8.13 10 | * Updated Android Gradle Plugin version to 8.12.0 11 | * Updated Android sdk version to 3.1.1 12 | 13 | ## 0.3.2 14 | 15 | * Updated Android Gradle version to 8.7 16 | * Update Android targetSdkVersion to 35 17 | 18 | ## 0.3.1 19 | 20 | * Updated iOS sdk version to 7.0.1 21 | * Updated Android sdk version to 3.1.0 22 | 23 | ## 0.3.0 24 | 25 | * Updated iOS sdk version to 7.0.0 26 | * Updated Android sdk version to 3.0.0 27 | 28 | ## 0.2.7 29 | 30 | * Fixed android SDK crash 31 | 32 | ## 0.2.6 33 | 34 | * Updated iOS sdk version to 6.0.6 35 | 36 | ## 0.2.5 37 | 38 | * Updated iOS sdk version to 6.0.5 39 | * Updated Android sdk version to 2.2.263 40 | 41 | ## 0.2.4 42 | 43 | * Updated iOS sdk version to 6.0.4 44 | * Updated Android sdk version to 2.2.262 45 | 46 | ## 0.2.3 47 | 48 | * Updated iOS sdk version to 6.0.3 49 | 50 | ## 0.2.2 51 | 52 | * Updated iOS sdk version to 6.0.2 53 | 54 | ## 0.2.1 55 | 56 | * Update Wrapper to support Flutter 3.22 57 | 58 | ## 0.2.0 59 | 60 | * Migrate to dart 3+ 61 | * Updated Android sdk version to 2.2.260 62 | * Updated iOS sdk version to 6.0.0 63 | 64 | ## 0.1.1 65 | 66 | * Updated Android sdk version to 2.2.253 67 | 68 | ## 0.1.0 69 | 70 | * Add `deviceID` methods: `setDeviceID`, `getDeviceID`, `clearDeviceID`. 71 | * Add permissions check method: `isAllRequiredPermissionsAndSensorsGranted`. 72 | * Add SDK status manipulation: `isSdkEnabled`, `setEnableSdk`. 73 | * Add tracking manipulation: `isTracking`, `startTracking`, `stopTracking`. 74 | * Add tags manipulation: `getTrackTags`, `addTrackTags`, `removeTrackTags`. 75 | * Add future tag manipulation: `getFutureTrackTags`, `addFutureTrackTag`, `removeFutureTrackTag`, `removeAllFutureTrackTags`. 76 | * Add PermissionWizard methods. 77 | * iOS: add `lowerPowerMode`. 78 | * iOS: add `setAggressiveHeartbeats` with brief documentation. 79 | 80 | ## 0.0.1 81 | 82 | * Initial version 83 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | compileSdkVersion 35 27 | namespace "com.telematicssdk.example" 28 | sourceSets { 29 | main.java.srcDirs += 'src/main/kotlin' 30 | } 31 | 32 | lintOptions { 33 | disable 'InvalidPackage' 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId "com.telematicssdk.example" 39 | minSdkVersion 24 40 | targetSdkVersion 35 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | } 44 | 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_17 47 | targetCompatibility JavaVersion.VERSION_17 48 | } 49 | 50 | kotlinOptions { 51 | jvmTarget = "17" 52 | } 53 | 54 | packagingOptions { 55 | exclude 'META-INF/INDEX.LIST' 56 | exclude 'META-INF/io.netty.versions.properties' 57 | } 58 | 59 | buildTypes { 60 | release { 61 | // TODO: Add your own signing config for the release build. 62 | // Signing with the debug keys for now, so `flutter run --release` works. 63 | signingConfig signingConfigs.debug 64 | shrinkResources false 65 | minifyEnabled false 66 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 67 | } 68 | } 69 | } 70 | 71 | flutter { 72 | source '../..' 73 | } 74 | 75 | dependencies { 76 | implementation "com.telematicssdk:tracking:3.1.1" 77 | implementation 'androidx.appcompat:appcompat:1.6.1' 78 | } 79 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/swiftlint.yml: -------------------------------------------------------------------------------- 1 | whitelist_rules: 2 | - attributes 3 | - class_delegate_protocol 4 | - closing_brace 5 | - closure_end_indentation 6 | - closure_parameter_position 7 | - closure_spacing 8 | - collection_alignment 9 | - colon 10 | - comma 11 | - conditional_returns_on_newline 12 | - control_statement 13 | - convenience_type 14 | - custom_rules 15 | - cyclomatic_complexity 16 | - discouraged_optional_boolean 17 | - duplicate_imports 18 | - empty_count 19 | - empty_parameters 20 | - empty_parentheses_with_trailing_closure 21 | - empty_string 22 | - explicit_init 23 | - file_length 24 | - first_where 25 | - force_cast 26 | - force_try 27 | - force_unwrapping 28 | - function_parameter_count 29 | - implicit_getter 30 | - implicitly_unwrapped_optional 31 | - inert_defer 32 | - large_tuple 33 | - last_where 34 | - leading_whitespace 35 | - legacy_cggeometry_functions 36 | - legacy_constant 37 | - legacy_constructor 38 | - legacy_hashing 39 | - legacy_nsgeometry_functions 40 | - line_length 41 | - literal_expression_end_indentation 42 | - mark 43 | - multiline_arguments 44 | - multiline_literal_brackets 45 | - notification_center_detachment 46 | - opening_brace 47 | - operator_usage_whitespace 48 | - redundant_discardable_let 49 | - redundant_optional_initialization 50 | - redundant_nil_coalescing 51 | - redundant_void_return 52 | - return_arrow_whitespace 53 | - shorthand_operator 54 | - statement_position 55 | - syntactic_sugar 56 | - todo 57 | - toggle_bool 58 | - trailing_comma 59 | - trailing_newline 60 | - trailing_semicolon 61 | - trailing_whitespace 62 | - unused_import 63 | - unused_optional_binding 64 | - unused_setter_value 65 | - vertical_whitespace 66 | - void_return 67 | - weak_delegate 68 | 69 | disabled_rules: # rule identifiers to exclude from running 70 | 71 | opt_in_rules: # some rules are only opt-in 72 | 73 | excluded: # paths to ignore during linting. Takes precedence over `included`. 74 | - fastlane 75 | - Pods 76 | - .bundle 77 | 78 | custom_rules: 79 | image_name_initialization: # Disable UIImage init from name 80 | included: ".*.swift" 81 | name: "Image initialization" 82 | regex: 'UIImage\(named:[^)]+\)' 83 | message: "Use UIImage(assetName: ) instead" 84 | severity: error 85 | 86 | open_iboutlets: 87 | included: ".*.swift" 88 | name: "IBOutlet opening" 89 | regex: "@IBOutlet ?(weak){0,1} var" 90 | message: "IBOutlet should be private or fileprivate" 91 | severity: error 92 | 93 | open_ibaction: 94 | included: ".*.swift" 95 | name: "IBAction opening" 96 | regex: "@IBAction func" 97 | message: "IBAction should be private or fileprivate" 98 | severity: error 99 | 100 | line_length: 120 101 | 102 | file_length: 103 | warning: 500 104 | error: 1200 105 | 106 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit) 107 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 17 | 25 | 29 | 32 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 55 | 57 | 63 | 64 | 65 | 66 | 72 | 74 | 80 | 81 | 82 | 83 | 85 | 86 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BGTaskSchedulerPermittedIdentifiers 6 | 7 | com.telematicssdk.flutterexample 8 | sdk.damoov.apprefreshtaskid 9 | sdk.damoov.appprocessingtaskid 10 | 11 | CADisableMinimumFrameDurationOnPhone 12 | 13 | CFBundleDevelopmentRegion 14 | $(DEVELOPMENT_LANGUAGE) 15 | CFBundleExecutable 16 | $(EXECUTABLE_NAME) 17 | CFBundleIdentifier 18 | $(PRODUCT_BUNDLE_IDENTIFIER) 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleName 22 | example 23 | CFBundlePackageType 24 | APPL 25 | CFBundleShortVersionString 26 | $(FLUTTER_BUILD_NAME) 27 | CFBundleSignature 28 | ???? 29 | CFBundleVersion 30 | $(FLUTTER_BUILD_NUMBER) 31 | LSRequiresIPhoneOS 32 | 33 | NSBluetoothAlwaysUsageDescription 34 | Please, provide permissions for this Demo 35 | NSBluetoothPeripheralUsageDescription 36 | Please, provide permissions for this Demo 37 | NSLocationAlwaysAndWhenInUseUsageDescription 38 | Please, provide permissions for this Demo 39 | NSLocationAlwaysUsageDescription 40 | Please, provide permissions for this Demo 41 | NSLocationWhenInUseUsageDescription 42 | Please, provide permissions for this Demo 43 | NSMotionUsageDescription 44 | Please, provide permissions for this Demo 45 | ITSAppUsesNonExemptEncryption 46 | 47 | UIApplicationSceneManifest 48 | 49 | UIApplicationSupportsMultipleScenes 50 | 51 | UISceneConfigurations 52 | 53 | UIWindowSceneSessionRoleApplication 54 | 55 | 56 | UISceneClassName 57 | UIWindowScene 58 | UISceneDelegateClassName 59 | FlutterSceneDelegate 60 | UISceneConfigurationName 61 | flutter 62 | UISceneStoryboardFile 63 | Main 64 | 65 | 66 | 67 | 68 | UIApplicationSupportsIndirectInputEvents 69 | 70 | UIBackgroundModes 71 | 72 | bluetooth-central 73 | fetch 74 | location 75 | processing 76 | remote-notification 77 | 78 | UILaunchStoryboardName 79 | LaunchScreen 80 | UIMainStoryboardFile 81 | Main 82 | UISupportedInterfaceOrientations 83 | 84 | UIInterfaceOrientationPortrait 85 | UIInterfaceOrientationLandscapeLeft 86 | UIInterfaceOrientationLandscapeRight 87 | 88 | UISupportedInterfaceOrientations~ipad 89 | 90 | UIInterfaceOrientationPortrait 91 | UIInterfaceOrientationPortraitUpsideDown 92 | UIInterfaceOrientationLandscapeLeft 93 | UIInterfaceOrientationLandscapeRight 94 | 95 | UIViewControllerBasedStatusBarAppearance 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 13 | 14 | 117 | 118 | 129 | 130 | -------------------------------------------------------------------------------- /lib/src/native_call_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:telematics_sdk/src/data/tag.dart'; 6 | import 'package:telematics_sdk/src/data/status.dart'; 7 | import 'package:telematics_sdk/src/data/future_track_callbacks.dart'; 8 | import 'package:telematics_sdk/src/data/permission_wizard_result.dart'; 9 | import 'package:telematics_sdk/src/data/track_location.dart'; 10 | 11 | import 'data/delegates_callbacks.dart'; 12 | 13 | class NativeCallHandler { 14 | OnTagAddCallback? onTagAdd; 15 | OnTagRemoveCallback? onTagRemove; 16 | OnAllTagsRemoveCallback? onAllTagsRemove; 17 | OnGetTagsCallback? onGetTags; 18 | 19 | Stream get onPermissionWizardClose => 20 | _onPermissionWizardClose.stream; 21 | Stream get lowerPowerMode => _onLowerPowerMode.stream; 22 | Stream get locationChanged => _onLocationChanged.stream; 23 | //Stream get newEvents => _onNewEvents.stream; //TO DO 24 | // Stream get wrongAccuracyAuthorization => //TO DO 25 | // _onWrongAccuracyAuthorization.stream; 26 | Stream get trackingStateChanged => _onTrackingStateChanged.stream; 27 | Stream get logEvent => _onLogEvent.stream; 28 | Stream get logWarning => _onLogWarning.stream; 29 | Stream get speedLimitNotification => 30 | _onSpeedLimitNotification.stream; 31 | Stream get heartbeatSent => _onHeartbeatSent.stream; 32 | Stream get rtldCollectedData => _onRtldCollectedData.stream; 33 | 34 | Future handle(MethodCall call) async { 35 | switch (call.method) { 36 | case 'onPermissionWizardResult': 37 | _onPermissionWizardResult(call); 38 | break; 39 | case 'onLowPowerMode': 40 | _onLowPowerMode(call); 41 | break; 42 | case 'onLocationChanged': 43 | _onLocationChangedHandler(call); 44 | break; 45 | case 'onNewEvents': 46 | _onNewEventsHandler(call); 47 | break; 48 | case 'onWrongAccuracyAuthorization': 49 | _onWrongAccuracyAuthorizationHandler(call); 50 | break; 51 | case 'onTrackingStateChanged': 52 | _onTrackingStateChangedHandler(call); 53 | break; 54 | case 'onLogEvent': 55 | _onLogEventHandler(call); 56 | break; 57 | case 'onLogWarning': 58 | _onLogWarningHandler(call); 59 | break; 60 | case 'onSpeedLimitNotification': 61 | _onSpeedLimitNotificationHandler(call); 62 | break; 63 | case 'onHeartbeatSent': 64 | _onHeartbeatSentHandler(call); 65 | break; 66 | case 'onRtldCollectedData': 67 | _onRtldCollectedDataHandler(call); 68 | break; 69 | 70 | case 'onTagAdd': 71 | _onTagAdd(call); 72 | break; 73 | case 'onTagRemove': 74 | _onTagRemove(call); 75 | break; 76 | case 'onAllTagsRemove': 77 | _onAllTagsRemove(call); 78 | break; 79 | case 'onGetTags': 80 | _onGetTags(call); 81 | break; 82 | } 83 | return Object(); 84 | } 85 | 86 | final _onPermissionWizardClose = 87 | StreamController.broadcast(); 88 | final _onLowerPowerMode = StreamController.broadcast(); 89 | 90 | final _onLocationChanged = StreamController.broadcast(); 91 | //final _onNewEvents = StreamController.broadcast(); //TO DO 92 | //final _onWrongAccuracyAuthorization = StreamController.broadcast(); //TO DO 93 | final _onTrackingStateChanged = StreamController.broadcast(); 94 | final _onLogEvent = StreamController.broadcast(); 95 | final _onLogWarning = StreamController.broadcast(); 96 | final _onSpeedLimitNotification = 97 | StreamController.broadcast(); 98 | final _onHeartbeatSent = StreamController.broadcast(); 99 | final _onRtldCollectedData = StreamController.broadcast(); 100 | 101 | void _onPermissionWizardResult(MethodCall call) { 102 | const wizardResultMapping = { 103 | 'WIZARD_RESULT_ALL_GRANTED': PermissionWizardResult.allGranted, 104 | 'WIZARD_RESULT_NOT_ALL_GRANTED': PermissionWizardResult.notAllGranted, 105 | 'WIZARD_RESULT_CANCELED': PermissionWizardResult.canceled, 106 | }; 107 | 108 | final argument = call.arguments as String; 109 | if (wizardResultMapping.containsKey(argument)) { 110 | _onPermissionWizardClose.add(wizardResultMapping[argument]!); 111 | } 112 | } 113 | 114 | void _onLowPowerMode(MethodCall call) { 115 | final state = call.arguments as bool; 116 | _onLowerPowerMode.add(state); 117 | } 118 | 119 | void _onLocationChangedHandler(MethodCall call) { 120 | final latitude = call.arguments['latitude'] as double; 121 | final longitude = call.arguments['longitude'] as double; 122 | final location = TrackLocation(latitude: latitude, longitude: longitude); 123 | _onLocationChanged.add(location); 124 | } 125 | 126 | void _onNewEventsHandler(MethodCall call) {} //TO DO 127 | 128 | void _onWrongAccuracyAuthorizationHandler(MethodCall call) {} //TO DO 129 | 130 | void _onTrackingStateChangedHandler(MethodCall call) { 131 | final state = call.arguments as bool; 132 | _onTrackingStateChanged.add(state); 133 | } 134 | 135 | void _onLogEventHandler(MethodCall call) { 136 | final state = call.arguments as String; 137 | _onLogEvent.add(state); 138 | } 139 | 140 | void _onLogWarningHandler(MethodCall call) { 141 | final state = call.arguments as String; 142 | _onLogWarning.add(state); 143 | } 144 | 145 | void _onSpeedLimitNotificationHandler(MethodCall call) { 146 | final speedLimit = call.arguments['speedLimit'] as double; 147 | final speed = call.arguments['speed'] as double; 148 | final latitude = call.arguments['latitude'] as double; 149 | final longitude = call.arguments['longitude'] as double; 150 | final dateUtc = call.arguments['date'] as String; 151 | final result = SpeedLimitNotificationResult( 152 | speedLimit: speedLimit, 153 | speed: speed, 154 | latitude: latitude, 155 | longitude: longitude, 156 | date: dateUtc); 157 | _onSpeedLimitNotification.add(result); 158 | } 159 | 160 | void _onHeartbeatSentHandler(MethodCall call) { 161 | final state = call.arguments['state'] as bool; 162 | final success = call.arguments['success'] as bool; 163 | final result = HeartbeatSentResult(state: state, success: success); 164 | _onHeartbeatSent.add(result); 165 | } 166 | 167 | void _onRtldCollectedDataHandler(MethodCall call) { 168 | final state = call.arguments as bool; 169 | _onRtldCollectedData.add(state); 170 | } 171 | 172 | void _onTagAdd(MethodCall call) { 173 | final _status = call.arguments['status'] as String; 174 | final _tagString = call.arguments['tag'] as String; 175 | 176 | final _tag = jsonDecode(_tagString) as Map; 177 | 178 | final status = Status.fromString(_status); 179 | final tag = Tag.fromJson(_tag); 180 | final activationTime = call.arguments['activationTime'] as int; 181 | 182 | onTagAdd?.call(status, tag, activationTime); 183 | } 184 | 185 | void _onTagRemove(MethodCall call) { 186 | final _status = call.arguments['status'] as String; 187 | final _tagString = call.arguments['tag'] as String; 188 | 189 | final _tag = jsonDecode(_tagString) as Map; 190 | 191 | final status = Status.fromString(_status); 192 | final tag = Tag.fromJson(_tag); 193 | final deactivationTime = call.arguments['deactivationTime'] as int; 194 | 195 | onTagRemove?.call(status, tag, deactivationTime); 196 | } 197 | 198 | void _onAllTagsRemove(MethodCall call) { 199 | final _status = call.arguments['status'] as String; 200 | 201 | final status = Status.fromString(_status); 202 | final deactivatedTagsCount = call.arguments['deactivatedTagsCount'] as int; 203 | final time = call.arguments['time'] as int; 204 | 205 | onAllTagsRemove?.call(status, deactivatedTagsCount, time); 206 | } 207 | 208 | void _onGetTags(MethodCall call) { 209 | final _status = call.arguments['status'] as String; 210 | final _tags = call.arguments['tags'] as List?; 211 | 212 | final status = Status.fromString(_status); 213 | final tags = _tags 214 | ?.map((e) => Tag.fromJson(jsonDecode(e) as Map)) 215 | .toList(); 216 | final time = call.arguments['time'] as int; 217 | 218 | onGetTags?.call(status, tags, time); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /test/native_call_handler_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:telematics_sdk/telematics_sdk.dart'; 6 | import 'package:telematics_sdk/src/native_call_handler.dart'; 7 | 8 | void main() { 9 | group('NativeCallHandler', () { 10 | late NativeCallHandler handler; 11 | 12 | setUp(() { 13 | handler = NativeCallHandler(); 14 | }); 15 | 16 | group('onPermissionWizardClose', () { 17 | late List events; 18 | 19 | setUp(() { 20 | events = []; 21 | }); 22 | 23 | test('emits allGranted', () async { 24 | handler.onPermissionWizardClose.listen( 25 | expectAsync1(events.add), 26 | ); 27 | 28 | await handler.handle( 29 | const MethodCall( 30 | 'onPermissionWizardResult', 31 | 'WIZARD_RESULT_ALL_GRANTED', 32 | ), 33 | ); 34 | 35 | expect(events, equals([PermissionWizardResult.allGranted])); 36 | }); 37 | 38 | test('emits notAllGranted', () async { 39 | handler.onPermissionWizardClose.listen( 40 | expectAsync1(events.add), 41 | ); 42 | 43 | await handler.handle( 44 | const MethodCall( 45 | 'onPermissionWizardResult', 46 | 'WIZARD_RESULT_NOT_ALL_GRANTED', 47 | ), 48 | ); 49 | 50 | expect(events, equals([PermissionWizardResult.notAllGranted])); 51 | }); 52 | 53 | test('emits all values', () async { 54 | const wizardResults = [ 55 | 'WIZARD_RESULT_ALL_GRANTED', 56 | 'WIZARD_RESULT_NOT_ALL_GRANTED', 57 | 'WIZARD_RESULT_CANCELED', 58 | ]; 59 | 60 | handler.onPermissionWizardClose.listen( 61 | expectAsync1( 62 | events.add, 63 | count: wizardResults.length, 64 | ), 65 | ); 66 | 67 | for (final actual in wizardResults) { 68 | await handler.handle( 69 | MethodCall( 70 | 'onPermissionWizardResult', 71 | actual, 72 | ), 73 | ); 74 | } 75 | 76 | expect( 77 | events, 78 | equals([ 79 | PermissionWizardResult.allGranted, 80 | PermissionWizardResult.notAllGranted, 81 | PermissionWizardResult.canceled, 82 | ])); 83 | }); 84 | }); 85 | 86 | group('lowerPowerMode', () { 87 | late List events; 88 | 89 | setUp(() { 90 | events = []; 91 | }); 92 | 93 | test('emits true when handles corresponding value', () async { 94 | handler.lowerPowerMode.listen( 95 | expectAsync1(events.add), 96 | ); 97 | 98 | await handler.handle( 99 | const MethodCall('onLowPowerMode', true), 100 | ); 101 | 102 | expect(events, equals([true])); 103 | }); 104 | 105 | test('emits all values', () async { 106 | const testValues = [true, false]; 107 | 108 | handler.lowerPowerMode.listen( 109 | expectAsync1( 110 | events.add, 111 | count: testValues.length, 112 | ), 113 | ); 114 | 115 | for (final value in testValues) { 116 | await handler.handle( 117 | MethodCall('onLowPowerMode', value), 118 | ); 119 | } 120 | 121 | expect(events, equals([true, false])); 122 | }); 123 | }); 124 | 125 | group("doesn't break down with empty callbacks:", () { 126 | test('empty onTagAdd', () async { 127 | final argument = { 128 | 'status': 'SUCCESS', 129 | 'tag': jsonEncode({'source': 'source', 'tag': 'tag'}), 130 | 'activationTime': 1, 131 | }; 132 | 133 | await handler.handle(MethodCall('onTagAdd', argument)); 134 | }); 135 | 136 | test('empty onTagRemove', () async { 137 | final argument = { 138 | 'status': 'SUCCESS', 139 | 'tag': jsonEncode({'source': 'source', 'tag': 'tag'}), 140 | 'deactivationTime': 1, 141 | }; 142 | 143 | await handler.handle(MethodCall('onTagRemove', argument)); 144 | }); 145 | 146 | test('empty onAllTagsRemove', () async { 147 | final argument = { 148 | 'status': 'SUCCESS', 149 | 'deactivatedTagsCount': 1, 150 | 'time': 2, 151 | }; 152 | 153 | await handler.handle(MethodCall('onAllTagsRemove', argument)); 154 | }); 155 | 156 | test('empty onGetTags', () async { 157 | final argument = { 158 | 'status': 'SUCCESS', 159 | 'tags': List.generate( 160 | 3, 161 | (index) => 162 | jsonEncode({'source': 'source$index', 'tag': 'tag$index'}), 163 | ), 164 | 'time': 2, 165 | }; 166 | 167 | await handler.handle(MethodCall('onGetTags', argument)); 168 | }); 169 | }); 170 | 171 | group('onTagAdd', () { 172 | late Map result; 173 | 174 | setUp(() { 175 | handler.onTagAdd = (status, tag, activationTime) { 176 | result = { 177 | 'status': status, 178 | 'tag': tag, 179 | 'activationTime': activationTime, 180 | }; 181 | }; 182 | }); 183 | 184 | test('emits result with success status', () async { 185 | final argument = { 186 | 'status': 'SUCCESS', 187 | 'tag': jsonEncode({'source': 'source', 'tag': 'tag'}), 188 | 'activationTime': 1, 189 | }; 190 | 191 | await handler.handle(MethodCall('onTagAdd', argument)); 192 | 193 | expect(result['status'], equals(Status.success)); 194 | expect(result['tag'], isA()); 195 | expect(result['activationTime'], equals(1)); 196 | }); 197 | 198 | test('emits result with someError status', () async { 199 | final argument = { 200 | 'status': '42', 201 | 'tag': jsonEncode({'source': 'source', 'tag': 'tag'}), 202 | 'activationTime': 1, 203 | }; 204 | 205 | await handler.handle(MethodCall('onTagAdd', argument)); 206 | 207 | expect(result['status'], equals(Status.someError)); 208 | expect(result['tag'], isA()); 209 | expect(result['activationTime'], equals(1)); 210 | }); 211 | }); 212 | 213 | group('onTagRemove', () { 214 | late Map result; 215 | 216 | setUp(() { 217 | handler.onTagRemove = (status, tag, deactivationTime) { 218 | result = { 219 | 'status': status, 220 | 'tag': tag, 221 | 'deactivationTime': deactivationTime, 222 | }; 223 | }; 224 | }); 225 | 226 | test('emits result with success status', () async { 227 | final argument = { 228 | 'status': 'SUCCESS', 229 | 'tag': jsonEncode({'source': 'source', 'tag': 'tag'}), 230 | 'deactivationTime': 1, 231 | }; 232 | 233 | await handler.handle(MethodCall('onTagRemove', argument)); 234 | 235 | expect(result['status'], equals(Status.success)); 236 | expect(result['tag'], isA()); 237 | expect(result['deactivationTime'], equals(1)); 238 | }); 239 | 240 | test('emits result with someError status', () async { 241 | final argument = { 242 | 'status': '42', 243 | 'tag': jsonEncode({'source': 'source', 'tag': 'tag'}), 244 | 'deactivationTime': 1, 245 | }; 246 | 247 | await handler.handle(MethodCall('onTagRemove', argument)); 248 | 249 | expect(result['status'], equals(Status.someError)); 250 | expect(result['tag'], isA()); 251 | expect(result['deactivationTime'], equals(1)); 252 | }); 253 | }); 254 | 255 | group('onAllTagsRemove', () { 256 | late Map result; 257 | 258 | setUp(() { 259 | handler.onAllTagsRemove = (status, deactivatedTagsCount, time) { 260 | result = { 261 | 'status': status, 262 | 'deactivatedTagsCount': deactivatedTagsCount, 263 | 'time': time, 264 | }; 265 | }; 266 | }); 267 | 268 | test('emits result with success status', () async { 269 | final argument = { 270 | 'status': 'SUCCESS', 271 | 'deactivatedTagsCount': 1, 272 | 'time': 2, 273 | }; 274 | 275 | await handler.handle(MethodCall('onAllTagsRemove', argument)); 276 | 277 | expect(result['status'], equals(Status.success)); 278 | expect(result['deactivatedTagsCount'], equals(1)); 279 | expect(result['time'], equals(2)); 280 | }); 281 | 282 | test('emits result with someError status', () async { 283 | final argument = { 284 | 'status': '42', 285 | 'deactivatedTagsCount': 1, 286 | 'time': 2, 287 | }; 288 | 289 | await handler.handle(MethodCall('onAllTagsRemove', argument)); 290 | 291 | expect(result['status'], equals(Status.someError)); 292 | expect(result['deactivatedTagsCount'], equals(1)); 293 | expect(result['time'], equals(2)); 294 | }); 295 | }); 296 | 297 | group('onGetTags', () { 298 | late Map result; 299 | 300 | setUp(() { 301 | handler.onGetTags = (status, tags, time) { 302 | result = { 303 | 'status': status, 304 | 'tags': tags, 305 | 'time': time, 306 | }; 307 | }; 308 | }); 309 | 310 | test('emits result with success status', () async { 311 | final argument = { 312 | 'status': 'SUCCESS', 313 | 'tags': List.generate( 314 | 3, 315 | (index) => 316 | jsonEncode({'source': 'source$index', 'tag': 'tag$index'}), 317 | ), 318 | 'time': 2, 319 | }; 320 | 321 | await handler.handle(MethodCall('onGetTags', argument)); 322 | 323 | expect(result['status'], equals(Status.success)); 324 | expect(result['tags'], isA>()); 325 | expect((result['tags'] as List).length, 3); 326 | expect(result['time'], equals(2)); 327 | }); 328 | 329 | test('emits result with someError status', () async { 330 | final argument = { 331 | 'status': '42', 332 | 'tags': List.generate( 333 | 3, 334 | (index) => 335 | jsonEncode({'source': 'source$index', 'tag': 'tag$index'}), 336 | ), 337 | 'time': 2, 338 | }; 339 | 340 | await handler.handle(MethodCall('onGetTags', argument)); 341 | 342 | expect(result['status'], equals(Status.someError)); 343 | expect(result['tags'], isA>()); 344 | expect((result['tags'] as List).length, 3); 345 | expect(result['time'], equals(2)); 346 | }); 347 | }); 348 | }); 349 | } 350 | -------------------------------------------------------------------------------- /lib/src/tracking_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:telematics_sdk/src/data/accident_detection_sensitivity.dart'; 6 | import 'package:telematics_sdk/src/data/api_language.dart'; 7 | import 'package:telematics_sdk/src/data/track_tag.dart'; 8 | import 'package:telematics_sdk/src/native_call_handler.dart'; 9 | import 'package:telematics_sdk/src/data/future_track_callbacks.dart'; 10 | import 'package:telematics_sdk/src/data/permission_wizard_result.dart'; 11 | import 'package:telematics_sdk/src/data/track_processed.dart'; 12 | 13 | import 'data/delegates_callbacks.dart'; 14 | import 'data/track_location.dart'; 15 | 16 | class TrackingApi { 17 | static const _channel = MethodChannel('telematics_sdk'); 18 | final NativeCallHandler _handler = NativeCallHandler(); 19 | 20 | OnTagAddCallback? onTagAdd; 21 | OnTagRemoveCallback? onTagRemove; 22 | OnAllTagsRemoveCallback? onAllTagsRemove; 23 | OnGetTagsCallback? onGetTags; 24 | 25 | TrackingApi() { 26 | _handler 27 | ..onTagAdd = onTagAdd 28 | ..onTagRemove = onTagRemove 29 | ..onAllTagsRemove = onAllTagsRemove 30 | ..onGetTags = onGetTags; 31 | 32 | _channel.setMethodCallHandler(_handler.handle); 33 | } 34 | 35 | Stream get onPermissionWizardClose => 36 | _handler.onPermissionWizardClose; 37 | Stream get lowerPowerMode => _handler.lowerPowerMode; 38 | Stream get locationChanged => _handler.locationChanged; 39 | //Stream get newEvents => _handler.newEvents; //TO DO 40 | // Stream get wrongAccuracyAuthorization => //TO DO 41 | // _handler.wrongAccuracyAuthorization; 42 | Stream get trackingStateChanged => _handler.trackingStateChanged; 43 | Stream get logEvent => _handler.logEvent; 44 | Stream get logWarning => _handler.logWarning; 45 | Stream get speedLimitNotification => 46 | _handler.speedLimitNotification; 47 | Stream get heartbeatSent => _handler.heartbeatSent; 48 | Stream get rtldCollectedData => _handler.rtldCollectedData; 49 | 50 | /* 51 | Initializes new RPEntry class instance with specified device ID. Must be the first method calling from Telematics SDK. 52 | */ 53 | Future initializeSdk() => 54 | _channel 55 | .invokeMethod('initializeSdk'); 56 | 57 | Future getSdkVersion() => _channel.invokeMethod('getSdkVersion'); 58 | 59 | Future getDeviceId() => _channel.invokeMethod('getDeviceId'); 60 | 61 | Future clearDeviceID() => _channel.invokeMethod('clearDeviceID'); 62 | 63 | Future setDeviceID({required String deviceId}) => 64 | _channel.invokeMethod('setDeviceID', {'deviceId': deviceId}); 65 | 66 | Future getApiLanguage() { 67 | return _channel.invokeMethod('getApiLanguage').then((value) { 68 | if (value == "None") { 69 | return ApiLanguage.none; 70 | } else if (value == "English") { 71 | return ApiLanguage.english; 72 | } else if (value == "Russian") { 73 | return ApiLanguage.russian; 74 | } else if (value == "Portuguese") { 75 | return ApiLanguage.portuguese; 76 | } else if (value == "Spanish") { 77 | return ApiLanguage.spanish; 78 | } else { 79 | return null; 80 | } 81 | }); 82 | } 83 | 84 | Future setApiLanguage({required ApiLanguage language}) { 85 | var apiLanguage = ''; 86 | switch (language) { 87 | case ApiLanguage.none: 88 | apiLanguage = 'None'; 89 | case ApiLanguage.english: 90 | apiLanguage = 'English'; 91 | case ApiLanguage.russian: 92 | apiLanguage = 'Russian'; 93 | case ApiLanguage.portuguese: 94 | apiLanguage = 'Portuguese'; 95 | case ApiLanguage.spanish: 96 | apiLanguage = 'Spanish'; 97 | } 98 | return _channel 99 | .invokeMethod('setApiLanguage', {'apiLanguage': apiLanguage}); 100 | } 101 | 102 | Future isWrongAccuracyState() => 103 | _channel.invokeMethod('isWrongAccuracyState'); 104 | 105 | Future isAllRequiredPermissionsAndSensorsGranted() => 106 | _channel.invokeMethod('isAllRequiredPermissionsAndSensorsGranted'); 107 | 108 | Future isSdkEnabled() => _channel.invokeMethod('isSdkEnabled'); 109 | 110 | Future isTracking() => _channel.invokeMethod('isTracking'); 111 | 112 | ///SDK will be just enabled or disabled 113 | /// depending on [enable] value. 114 | Future setEnableSdk({ 115 | required bool enable 116 | }) => 117 | _channel.invokeMethod('setEnableSdk', { 118 | 'enable': enable 119 | }); 120 | 121 | /// Disable SDK with enforced trip uploading 122 | /// using this method, SDK will enforce trip uploading and then will be disabled. 123 | Future setDisableWithUpload() => _channel.invokeMethod('setDisableWithUpload'); 124 | 125 | Future setDisableTracking({required bool value}) => 126 | _channel.invokeMethod('setDisableTracking', {'value': value}); 127 | 128 | Future isDisableTracking() => _channel.invokeMethod('isDisableTracking'); 129 | 130 | Future startManualTracking() => _channel.invokeMethod('startManualTracking'); 131 | 132 | Future startManualPersistentTracking() => _channel.invokeMethod('startManualPersistentTracking'); 133 | 134 | Future stopManualTracking() => _channel.invokeMethod('stopManualTracking'); 135 | 136 | Future uploadUnsentTrips() => _channel.invokeMethod('uploadUnsentTrips'); 137 | 138 | Future getUnsentTripCount() => _channel.invokeMethod('getUnsentTripCount'); 139 | 140 | Future sendCustomHeartbeats({required String reason}) => 141 | _channel.invokeMethod('sendCustomHeartbeats', {'reason': reason}); 142 | 143 | /// `SDK can work in two modes`: 144 | /// `Aggressive` - heartbeats are sent every 20 minutes and SDK never sleeps. 145 | /// `Normal` - heartbeats are sent every 20 minutes but when system suspends SDK, 146 | /// it gees to a sleep mode and will restore work only in trip start time. 147 | Future isAggressiveHeartbeat() => 148 | _channel.invokeMethod('isAggressiveHeartbeat'); 149 | 150 | //TO DO - change to return void (check) 151 | Future setAggressiveHeartbeats({required bool value}) => 152 | _channel.invokeMethod('setAggressiveHeartbeats', {'value': value}); 153 | 154 | Future enableELM({required bool value}) => 155 | _channel.invokeMethod('enableELM', {'enableELM': value}); 156 | 157 | Future isEnabledELM() => _channel.invokeMethod('isEnabledELM'); 158 | 159 | Future enableAccidents({required bool value}) => 160 | _channel.invokeMethod('enableAccidents', {'enableAccidents': value}); 161 | 162 | Future isEnabledAccidents() => 163 | _channel.invokeMethod('isEnabledAccidents'); 164 | 165 | Future setAccidentDetectionSensitivity({required AccidentDetectionSensitivity sensitivity}) { 166 | int value = 0; 167 | switch (sensitivity) { 168 | case AccidentDetectionSensitivity.normal: 169 | value = 0; 170 | case AccidentDetectionSensitivity.sensitive: 171 | value = 1; 172 | case AccidentDetectionSensitivity.tough: 173 | value = 2; 174 | } 175 | return _channel 176 | .invokeMethod('setAccidentDetectionSensitivity', {'accidentDetectionSensitivity': value}); 177 | } 178 | 179 | Future isRTLDEnabled() => _channel.invokeMethod('isRTLDEnabled'); 180 | 181 | /// If [enableAggressivePermissionsWizard] set to `true` the wizard will be 182 | /// finished if all required permissions granted (user can’t cancel it with 183 | /// back button), otherwise if set to `false` the wizard can be finished with 184 | /// not all granted permissions or cancelled with back button. 185 | /// 186 | /// If [enableAggressivePermissionsWizardPage] set to `true` the wizard will 187 | /// slide to next page if requested permissions granted on current page, 188 | /// otherwise if set to `false` the wizard can slide with not granted permissions. 189 | Future showPermissionWizard({ 190 | required bool enableAggressivePermissionsWizard, 191 | required bool enableAggressivePermissionsWizardPage, 192 | }) => 193 | _channel.invokeMethod('showPermissionWizard', { 194 | 'enableAggressivePermissionsWizard': enableAggressivePermissionsWizard, 195 | 'enableAggressivePermissionsWizardPage': 196 | enableAggressivePermissionsWizardPage, 197 | }); 198 | 199 | ///Tracks 200 | 201 | //Requests all tracks with specified offset and limit. Can be filtered by passing non-nil start & end dates. 202 | 203 | //TO DO - add getTracks function to SwiftTelematicsSDKPlugin 204 | Future?> getTracks({ 205 | required int offset, 206 | required int limit, 207 | DateTime? startDate, 208 | DateTime? endDate 209 | }) => _channel.invokeMethod>('getTracks', { 210 | 'offset': offset, 211 | 'limit': limit, 212 | 'startDate': startDate?.toUtc().toIso8601String(), 213 | 'endDate': endDate?.toUtc().toIso8601String() 214 | }).then( 215 | (value) => value?.map( 216 | (e) => TrackProcessed.fromJson(jsonDecode(e) as Map), 217 | ), 218 | ); 219 | 220 | ///TrackTags 221 | Future?> getTrackTags({required String trackId}) => 222 | _channel.invokeMethod>('getTrackTags', { 223 | 'trackId': trackId, 224 | }).then( 225 | (value) => value?.map( 226 | (e) => TrackTag.fromJson(jsonDecode(e) as Map), 227 | ), 228 | ); 229 | 230 | Future?> addTrackTags({ 231 | required String trackId, 232 | required Iterable tags, 233 | }) => 234 | _channel.invokeMethod>('addTrackTags', { 235 | 'trackId': trackId, 236 | 'tags': tags.map((it) => jsonEncode(it.toJson)) 237 | }).then( 238 | (value) => value?.map( 239 | (e) => TrackTag.fromJson(jsonDecode(e) as Map), 240 | ), 241 | ); 242 | 243 | Future?> removeTrackTags({ 244 | required String trackId, 245 | required Iterable tags, 246 | }) => 247 | _channel.invokeMethod>('removeTrackTags', { 248 | 'trackId': trackId, 249 | 'tags': tags.map((it) => jsonEncode(it.toJson)) 250 | }).then( 251 | (value) => value?.map( 252 | (e) => TrackTag.fromJson(jsonDecode(e) as Map), 253 | ), 254 | ); 255 | 256 | ///FutureTrackTags 257 | Future getFutureTrackTags() => 258 | _channel.invokeMethod('getFutureTrackTags'); 259 | 260 | Future addFutureTrackTag({ 261 | required String tag, 262 | required String source, 263 | }) => 264 | _channel.invokeMethod('addFutureTrackTag', { 265 | 'tag': tag, 266 | 'source': source, 267 | }); 268 | 269 | Future removeFutureTrackTag({required String tag}) => 270 | _channel.invokeMethod('removeFutureTrackTag', {'tag': tag}); 271 | 272 | /// TODO: check iOS 273 | Future removeAllFutureTrackTags() => 274 | _channel.invokeMethod('removeAllFutureTrackTags'); 275 | } 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telematics SDK 2 | 3 | A flutter plugin for tracking the person's driving behavior such as speeding, turning, braking and several other things on iOS and Android. 4 | 5 | __Disclaimer__: This project uses Telematics SDK which belongs to DAMOOV PTE. LTD. 6 | When using Telematics SDK refer to these [terms of use](https://docs.damoov.com/docs/license) 7 | 8 | ## Getting Started 9 | 10 | ### Initial app setup & credentials 11 | 12 | For commercial use, you need create a developer workspace in [DataHub](https://app.damoov.com) and get `InstanceId` and `InstanceKey` auth keys to work with our API. 13 | 14 | ### Android 15 | 16 | #### Please draw attention that Android SDK supports Gradle 8+ versions only. 17 | 18 | #### AndroidManifest.xml 19 | 20 | add to file ./app/src/main/AndroidManifest.xml props: 21 | 22 | 1. 'xmlns:tools="http://schemas.android.com/tools"' into __manifest__ tag 23 | 2. 'tools:replace="android:label"' into __application tag 24 | 25 | as shown below: 26 | 27 | ``` xml 28 | 30 | 32 | ... 33 | 34 | ... 35 | 36 | 37 | ``` 38 | 39 | add network permissions 40 | 41 | ``` xml 42 | 43 | ... 44 | 45 | 46 | 47 | ... 48 | ``` 49 | 50 | #### build.gradle 51 | 52 | add to file (module)/build.gradle props: 53 | 54 | ``` groovy 55 | android { 56 | ... 57 | buildTypes { 58 | release { 59 | ... 60 | shrinkResources false 61 | minifyEnabled false 62 | ... 63 | } 64 | } 65 | ... 66 | } 67 | ``` 68 | 69 | #### Proguard 70 | 71 | ``` markdown 72 | -keep public class com.telematicssdk.tracking.** {*;} 73 | ``` 74 | 75 | ### Android Advanced 76 | 77 | #### SetTrackingSettings 78 | 79 | 1. Override application class extends __TelematicsSDKApp__ 80 | 81 | ``` kotlin 82 | import com.telematicssdk.TelematicsSDKApp 83 | 84 | class App: TelematicsSDKApp() { 85 | 86 | override fun onCreate() { 87 | val api = TrackingApi.getInstance() 88 | api.initialize(this, setTelematicsSettings()) 89 | super.onCreate() 90 | } 91 | 92 | override fun setTelematicsSettings(): Settings { 93 | val settings = Settings( 94 | stopTrackingTimeout = Settings.stopTrackingTimeHigh, 95 | accuracy = Settings.accuracyHigh, 96 | autoStartOn = true, 97 | elmOn = false, 98 | hfOn = true 99 | ) 100 | return settings 101 | } 102 | } 103 | ``` 104 | 105 | 2. add to tag __application__ of file ./app/src/main/AndroidManifest.xml this class __name__: 106 | 107 | ``` xml 108 | 110 | ... 111 | 112 | 113 | ``` 114 | 115 | 3. add Telematics SDK repository into (module)/build.gradle 116 | 117 | ```groovy 118 | dependencies { 119 | //... 120 | implementation "com.telematicssdk:tracking:3.1.1" 121 | } 122 | ``` 123 | 124 | ### iOS 125 | 126 | Add permissions in your project's `ios/Runner/Info.plist`: 127 | 128 | ``` xml 129 | UIBackgroundModes 130 | 131 | fetch 132 | location 133 | remote-notification 134 | 135 | NSMotionUsageDescription 136 | Please, provide permissions for this Demo 137 | NSLocationWhenInUseUsageDescription 138 | Please, provide permissions for this Demo 139 | NSLocationAlwaysUsageDescription 140 | Please, provide permissions for this Demo 141 | NSLocationAlwaysAndWhenInUseUsageDescription 142 | Please, provide permissions for this Demo 143 | BGTaskSchedulerPermittedIdentifiers 144 | 145 | sdk.damoov.apprefreshtaskid 146 | sdk.damoov.appprocessingtaskid 147 | 148 | ``` 149 | Starting from iOS version 15 and above, as well as Flutter 2.0.6, modification of `ios/Runner/AppDelegate.swift` is required 150 | You must request permissions for the application before GeneratedPluginRegistrant 151 | ```swift 152 | @main 153 | @objc class AppDelegate: FlutterAppDelegate { 154 | 155 | override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 156 | RPEntry.initializeSDK() 157 | RPEntry.instance.application(application, didFinishLaunchingWithOptions: launchOptions) 158 | GeneratedPluginRegistrant.register(with: self) 159 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 160 | } 161 | } 162 | ``` 163 | 164 | ### Enabling and Disabling the SDK 165 | 166 | Firstly, create trackingAPI object to interact with SDK 167 | ``` dart 168 | import 'package:telematics_sdk/telematics_sdk.dart'; 169 | 170 | final _trackingApi = TrackingApi(); 171 | ``` 172 | 173 | **Login** 174 | ``` dart 175 | await _trackingApi.setDeviceID(deviceId: "DEVICE_TOKEN"); 176 | ``` 177 | 178 | **Logout** 179 | ``` dart 180 | await _trackingApi.clearDeviceID(); 181 | ``` 182 | 183 | **Enable the SDK** 184 | 185 | ``` dart 186 | await _trackingApi.setEnableSdk(enable: true); 187 | ``` 188 | 189 | **Disable the SDK** 190 | 191 | ``` dart 192 | await _trackingApi.setEnableSdk(enable: false); 193 | ``` 194 | 195 | **Disable the SDK with force uploading data** 196 | 197 | ``` dart 198 | await _trackingApi.setDisableWithUpload(); 199 | ``` 200 | 201 | ### Available Methods 202 | 203 | **Manual start tracking** 204 | ``` dart 205 | await _trackingApi.startManualTracking(); 206 | ``` 207 | 208 | **Manual start persistent tracking** 209 | ``` dart 210 | await _trackingApi.startManualPersistentTracking(); 211 | ``` 212 | 213 | **Notes:** 214 | Persistent tracking ignores all stop tracking reasons and continues before 'stopTracking' method will be called. 215 | Max persistent tracking duration is 10 hours. 216 | 217 | **Manual stop tracking** 218 | ``` dart 219 | await _trackingApi.stopManualTracking(); 220 | ``` 221 | 222 | **Upload unsent trips** 223 | ``` dart 224 | await _trackingApi.uploadUnsentTrips(); 225 | ``` 226 | 227 | **Get unsent trips** 228 | ``` dart 229 | final unsentTripsCount = await _trackingApi.getUnsentTripCount(); 230 | ``` 231 | 232 | **Get unsent trips** 233 | ``` dart 234 | final unsentTripsCount = await _trackingApi.getUnsentTripCount(); 235 | ``` 236 | 237 | **Send a custom heartbeat** 238 | ``` dart 239 | String reason = 'CustomHeartbeat'; 240 | await _trackingApi.sendCustomHeartbeats(reason: reason); 241 | ``` 242 | 243 | **Tracking status** 244 | ``` dart 245 | final isTracking = await _trackingApi.isTracking(); 246 | ``` 247 | 248 | **Create new tag** 249 | The detailed information about using Tags is available [here](https://docs.damoov.com/docs/ios-sdk-incoming-tags) 250 | ``` dart 251 | String tag = 'TAG'; 252 | String source = 'App'; 253 | await _trackingApi.addFutureTrackTag(tag: tag, source: source); 254 | ``` 255 | 256 | **Remove a tag** 257 | ``` dart 258 | String tag = 'TAG'; 259 | await _trackingApi.removeFutureTrackTag(tag: tag); 260 | ``` 261 | 262 | **Remove all tags** 263 | ``` dart 264 | await _trackingApi.removeAllFutureTrackTags(); 265 | ``` 266 | 267 | **Setting up the permission wizard** 268 | Without these permissions SDK can not be enabled. 269 | If you want to use your own way to request permissions, you can skip this part. 270 | 271 | To show the permission wizard, follow next steps: 272 | 1. Create and init **StreamSubscription** in your widget 273 | ``` dart 274 | late StreamSubscription _onPermissionWizardStateChanged; 275 | 276 | @override 277 | void initState() { 278 | _onPermissionWizardStateChanged = _trackingApi 279 | .onPermissionWizardClose 280 | .listen(_onPermissionWizardResult); 281 | 282 | void _onPermissionWizardResult(PermissionWizardResult result) { 283 | if (result == PermissionWizardResult.allGranted) { 284 | //All permissions are granted. To do something here. 285 | } else { 286 | //Permissions are not granted. To do something here. 287 | } 288 | } 289 | ``` 290 | 2. Request to show the permission wizard 291 | 292 | ``` dart 293 | await _trackingApi.showPermissionWizard( 294 | enableAggressivePermissionsWizard: false, 295 | enableAggressivePermissionsWizardPage: true 296 | ); 297 | ``` 298 | If `[enableAggressivePermissionsWizard]` set to `true` the wizard will be finished if all required permissions granted (user can’t cancel it with back button), otherwise if set to `false` the wizard can be finished with not all granted permissions or cancelled with back button. 299 | 300 | If `[enableAggressivePermissionsWizardPage]` set to `true` the wizard will slide to next page if requested permissions granted on current page, otherwise if set to `false` the wizard can slide with not granted permissions. 301 | 302 | 303 | ### Available Methods (iOS only) 304 | 305 | **Enable/Disable Automatic tracking** 306 | ``` dart 307 | bool disableTracking = false; 308 | //true to disable automatic tracking (tracking is enabled by default) 309 | await _trackingApi.setDisableTracking(value: disableTracking); 310 | ``` 311 | 312 | **Automatic tracking status** 313 | ``` dart 314 | final isTrackingDisabled = await _trackingApi.isDisableTracking(); 315 | ``` 316 | 317 | **Enable/Disable Aggressive Heartbeats** 318 | 319 | The telematics SDK (iOS only) supports two operational modes for heartbeats; 320 | 321 | **Aggressive heartbeats** - heartbeats are sent every 20 minutes. SDK is always active. 322 | **Normal Heartbeats** - heartbeats are sent every 20 minutes but when SDK turns into Standby mode, it will be activated only by a new trip, and heartbeat will be sent respectively. 323 | 324 | **Mode switcher** 325 | 326 | ``` dart 327 | bool enable = true; //false to disable aggressive heartbeats 328 | await _trackingApi.setAggressiveHeartbeats(value: enable) 329 | ``` 330 | 331 | **Check state** 332 | ``` dart 333 | final isAggressiveHeartbeats = await _trackingApi.isAggressiveHeartbeat() 334 | ``` 335 | 336 | **Enable Accidents detection** 337 | Accidents detection is disabled by default. You can enable detection. 338 | In order for accidents detection to work, you need to [enable high-frequency data collection](https://docs.damoov.com/docs/methods-for-ios-app#enable-high-frequency-data-collection-hf) 339 | ``` dart 340 | await _trackingApi.enableAccidents(value: true); 341 | 342 | //to check current accidents status 343 | final isEnabledAccidents = await _trackingApi.isEnabledAccidents(); 344 | ``` 345 | 346 | **Change Accident detection sensitivity** 347 | Accident detection sensitivity is normal by default. You can change sensitivity. Sensitivity options which is available to apply: .normal, .sensitive, .tough 348 | ``` dart 349 | await _trackingApi.setAccidentDetectionSensitivity(sensitivity: AccidentDetectionSensitivity.normal); 350 | ``` 351 | 352 | ## Links 353 | 354 | [https://damoov.com](https://damoov.com/) 355 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/telematicssdk/TelematicsSDKPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.telematicssdk 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.annotation.NonNull 7 | import org.json.JSONObject 8 | import com.telematicssdk.tracking.TrackingApi 9 | import com.telematicssdk.tracking.server.model.sdk.TrackTag 10 | import com.telematicssdk.tracking.utils.permissions.PermissionsWizardActivity 11 | import com.telematicssdk.tracking.model.realtime.configuration.AccidentDetectionSensitivity 12 | 13 | import io.flutter.embedding.engine.plugins.FlutterPlugin 14 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 15 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 16 | import io.flutter.plugin.common.MethodCall 17 | import io.flutter.plugin.common.MethodChannel 18 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 19 | import io.flutter.plugin.common.MethodChannel.Result 20 | import io.flutter.plugin.common.PluginRegistry.ActivityResultListener 21 | 22 | class WizardConstants { 23 | companion object { 24 | const val allGranted = "WIZARD_RESULT_ALL_GRANTED" 25 | const val notAllGranted = "WIZARD_RESULT_NOT_ALL_GRANTED" 26 | const val canceled = "WIZARD_RESULT_CANCELED" 27 | } 28 | } 29 | 30 | /** TelematicsSDKPlugin */ 31 | class TelematicsSDKPlugin : ActivityAware, ActivityResultListener, FlutterPlugin, 32 | MethodCallHandler { 33 | /// The MethodChannel that will the communication between Flutter and native Android 34 | /// 35 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 36 | /// when the Flutter Engine is detached from the Activity 37 | private lateinit var activity: Activity 38 | private lateinit var channel: MethodChannel 39 | private lateinit var context: Context 40 | private lateinit var activityPluginBinding: ActivityPluginBinding 41 | 42 | private val api = TrackingApi.getInstance() 43 | 44 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 45 | activityPluginBinding = binding 46 | activityPluginBinding.addActivityResultListener(this) 47 | activity = binding.activity 48 | } 49 | 50 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 51 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telematics_sdk") 52 | channel.setMethodCallHandler(this) 53 | context = flutterPluginBinding.applicationContext 54 | 55 | /// register callbacks 56 | api.addTagsProcessingCallback(TagsProcessingListenerImp(channel)) 57 | api.setLocationListener(LocationListenerImp(channel)) 58 | } 59 | 60 | override fun onDetachedFromActivity() { 61 | activityPluginBinding.removeActivityResultListener(this) 62 | } 63 | 64 | override fun onDetachedFromActivityForConfigChanges() { 65 | activityPluginBinding.removeActivityResultListener(this) 66 | } 67 | 68 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 69 | channel.setMethodCallHandler(null) 70 | } 71 | 72 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 73 | TODO("Not yet implemented") 74 | } 75 | 76 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 77 | when (call.method) { 78 | "clearDeviceID" -> clearDeviceID(result) 79 | "getDeviceId" -> getDeviceId(result) 80 | "isAllRequiredPermissionsAndSensorsGranted" -> isAllRequiredPermissionsAndSensorsGranted( 81 | result, 82 | ) 83 | "isSdkEnabled" -> isSdkEnabled(result) 84 | "isTracking" -> isTracking(result) 85 | "setDeviceID" -> setDeviceID(call, result) 86 | "setEnableSdk" -> setEnableSdk(call, result) 87 | "setDisableWithUpload" -> setDisableWithUpload(result) 88 | "startManualTracking" -> startManualTracking(result) 89 | "startManualPersistentTracking" -> startManualPersistentTracking(result) 90 | "stopManualTracking" -> stopManualTracking(result) 91 | "showPermissionWizard" -> showPermissionWizard(call, result) 92 | "getTrackTags" -> getTrackTags(call, result) 93 | "addTrackTags" -> addTrackTags(call, result) 94 | "removeTrackTags" -> removeTrackTags(call, result) 95 | "getFutureTrackTags" -> getFutureTrackTags(result) 96 | "addFutureTrackTag" -> addFutureTrackTag(call, result) 97 | "removeFutureTrackTag" -> removeFutureTrackTag(call, result) 98 | "removeAllFutureTrackTags" -> removeAllFutureTrackTags(result) 99 | "uploadUnsentTrips" -> uploadUnsentTrips(result) 100 | "getUnsentTripCount" -> getUnsentTripCount(result) 101 | "sendCustomHeartbeats" -> sendCustomHeartbeats(call, result) 102 | "setAccidentDetectionSensitivity" -> setAccidentDetectionSensitivity(call, result) 103 | else -> result.notImplemented() 104 | } 105 | } 106 | 107 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { 108 | if (requestCode == PermissionsWizardActivity.WIZARD_PERMISSIONS_CODE) { 109 | lateinit var wizardResult: String 110 | 111 | when (resultCode) { 112 | PermissionsWizardActivity.WIZARD_RESULT_ALL_GRANTED -> wizardResult = 113 | WizardConstants.allGranted 114 | PermissionsWizardActivity.WIZARD_RESULT_NOT_ALL_GRANTED -> wizardResult = 115 | WizardConstants.notAllGranted 116 | PermissionsWizardActivity.WIZARD_RESULT_CANCELED -> wizardResult = 117 | WizardConstants.canceled 118 | } 119 | 120 | channel.invokeMethod("onPermissionWizardResult", wizardResult) 121 | return true 122 | } 123 | 124 | return false 125 | } 126 | 127 | private fun clearDeviceID(result: Result) { 128 | api.clearDeviceID() 129 | 130 | result.success(null) 131 | } 132 | 133 | private fun getDeviceId(result: Result) { 134 | val deviceId = api.getDeviceId() 135 | 136 | result.success(deviceId) 137 | } 138 | 139 | private fun isSdkEnabled(result: Result) { 140 | val isEnabled = api.isSdkEnabled() 141 | 142 | result.success(isEnabled) 143 | } 144 | 145 | private fun isTracking(result: Result) { 146 | val isTracking = api.isTracking() 147 | 148 | result.success(isTracking) 149 | } 150 | 151 | private fun isAllRequiredPermissionsAndSensorsGranted(result: Result) { 152 | val isGranted = api.isAllRequiredPermissionsAndSensorsGranted() 153 | 154 | result.success(isGranted) 155 | } 156 | 157 | private fun setDeviceID(call: MethodCall, result: Result) { 158 | val deviceId = call.argument("deviceId") as String 159 | 160 | api.setDeviceID(deviceId) 161 | 162 | result.success(null) 163 | } 164 | 165 | private fun setEnableSdk(call: MethodCall, result: Result) { 166 | val enable = call.argument("enable") as Boolean 167 | api.setEnableSdk(enable) 168 | result.success(null) 169 | } 170 | 171 | private fun setDisableWithUpload(result: Result) { 172 | api.setDisableWithUpload() 173 | result.success(null) 174 | } 175 | 176 | private fun startManualTracking(result: Result) { 177 | val startResult = api.startTracking() 178 | 179 | result.success(startResult) 180 | } 181 | 182 | private fun startManualPersistentTracking(result: Result) { 183 | val startResult = api.startPersistentTracking() 184 | 185 | result.success(startResult) 186 | } 187 | 188 | private fun stopManualTracking(result: Result) { 189 | val stopResult = api.stopTracking() 190 | 191 | result.success(stopResult) 192 | } 193 | 194 | private fun uploadUnsentTrips(result: Result) { 195 | api.uploadUnsentTrips() 196 | result.success(null) 197 | } 198 | 199 | private fun getUnsentTripCount(result: Result) { 200 | val count = api.getUnsentTripCount() 201 | result.success(count) 202 | } 203 | 204 | private fun sendCustomHeartbeats(call: MethodCall, result: Result) { 205 | val reason = call.argument("reason") as String 206 | api.sendCustomHeartbeats(reason) 207 | result.success(null) 208 | } 209 | 210 | private fun showPermissionWizard(call: MethodCall, result: Result) { 211 | val enableAggressivePermissionsWizard = 212 | call.argument("enableAggressivePermissionsWizard") as Boolean 213 | val enableAggressivePermissionsWizardPage = 214 | call.argument("enableAggressivePermissionsWizardPage") as Boolean 215 | 216 | activity.startActivityForResult( 217 | PermissionsWizardActivity.getStartWizardIntent( 218 | context, 219 | enableAggressivePermissionsWizard = enableAggressivePermissionsWizard, 220 | enableAggressivePermissionsWizardPage = enableAggressivePermissionsWizardPage, 221 | ), PermissionsWizardActivity.WIZARD_PERMISSIONS_CODE 222 | ) 223 | 224 | result.success(null) 225 | } 226 | 227 | private fun getTrackTags(call: MethodCall, result: Result) { 228 | val trackId = call.argument("trackId") as String 229 | 230 | val res = api.getTrackTags(trackId).map { it.toJsonString() }.toTypedArray() 231 | 232 | result.success(res) 233 | } 234 | 235 | private fun addTrackTags(call: MethodCall, result: Result) { 236 | val trackId = call.argument("trackId") as String 237 | val strings = call.argument?>("tags") as Array 238 | 239 | val tags = strings.map { 240 | val json = JSONObject(it) 241 | val tag = json["tag"] as String 242 | val source = json["source"] as String 243 | TrackTag(tag, source) 244 | }.toTypedArray() 245 | 246 | val res = api.addTrackTags(trackId, tags).map { it.toJsonString() }.toTypedArray() 247 | 248 | result.success(res) 249 | } 250 | 251 | private fun removeTrackTags(call: MethodCall, result: Result) { 252 | val trackId = call.argument("trackId") as String 253 | val strings = call.argument?>("tags") as Array 254 | 255 | val tags = strings.map { 256 | val json = JSONObject(it) 257 | val tag = json["tag"] as String 258 | val source = json["source"] as String 259 | TrackTag(tag, source) 260 | }.toTypedArray() 261 | 262 | val res = api.removeTrackTags(trackId, tags).map { it.toJsonString() }.toTypedArray() 263 | 264 | result.success(res) 265 | } 266 | 267 | private fun getFutureTrackTags(result: Result) { 268 | api.getFutureTrackTags() 269 | 270 | result.success(null) 271 | } 272 | 273 | private fun addFutureTrackTag(call: MethodCall, result: Result) { 274 | val tag = call.argument("tag") as String 275 | val source = call.argument("source") as String 276 | 277 | api.addFutureTrackTag(tag, source) 278 | 279 | result.success(null) 280 | } 281 | 282 | private fun removeFutureTrackTag(call: MethodCall, result: Result) { 283 | val tag = call.argument("tag") as String 284 | 285 | api.removeFutureTrackTag(tag) 286 | 287 | result.success(null) 288 | } 289 | 290 | private fun removeAllFutureTrackTags(result: Result) { 291 | api.removeAllFutureTrackTags() 292 | 293 | result.success(null) 294 | } 295 | 296 | private fun setAccidentDetectionSensitivity(call: MethodCall, result: Result) { 297 | val value = call.argument("accidentDetectionSensitivity") as Int 298 | 299 | val sensitivity = when (value) { 300 | 0 -> AccidentDetectionSensitivity.Normal 301 | 1 -> AccidentDetectionSensitivity.Sensitive 302 | 2 -> AccidentDetectionSensitivity.Tough 303 | else -> AccidentDetectionSensitivity.Normal 304 | } 305 | 306 | api.setAccidentDetectionMode(sensitivity) 307 | result.success(null) 308 | } 309 | 310 | } 311 | -------------------------------------------------------------------------------- /lib/src/data/track_processed.dart: -------------------------------------------------------------------------------- 1 | 2 | class TrackProcessed { 3 | final DateTime startDate; 4 | final DateTime endDate; 5 | 6 | /// The start address. 7 | final String addressStart; 8 | 9 | /// The end address. 10 | final String addressEnd; 11 | 12 | /// The count of sharp acceleration. 13 | final int accelerationCount; 14 | 15 | /// The count of sharp braking. 16 | final int decelerationCount; 17 | 18 | /// The trip rating. 19 | final double rating; 20 | 21 | /// The trip rating. 22 | final double rating100; 23 | 24 | /// The trip сornering rating. 25 | final double ratingCornering; 26 | 27 | /// The trip сornering rating. 28 | final double ratingCornering100; 29 | 30 | /// The trip acceleration rating. 31 | final double ratingAcceleration; 32 | 33 | /// The trip acceleration rating. 34 | final double ratingAcceleration100; 35 | 36 | /// The trip braking rating. 37 | final double ratingBraking; 38 | 39 | /// The trip braking rating. 40 | final double ratingBraking100; 41 | 42 | /// The trip speeding rating. 43 | final double ratingSpeeding; 44 | 45 | /// The trip speeding rating. 46 | final double ratingSpeeding100; 47 | 48 | /// The trip phone usage rating. 49 | final double ratingPhoneUsage; 50 | 51 | /// The trip phone usage rating. 52 | final double ratingPhoneUsage100; 53 | 54 | /// The trip time of day rating. 55 | final double ratingTimeOfDay; 56 | 57 | /// The trip reason rating. 58 | final String ratingReason; 59 | 60 | /// The trip reason rating. 61 | final AddressParts addressStartParts; 62 | 63 | /// The trip reason rating. 64 | final AddressParts addressFinishParts; 65 | 66 | final List tags; 67 | 68 | /// The trip phone usage minutes. 69 | final double phoneUsage; 70 | 71 | /// The distance trip in km. 72 | final double distance; 73 | 74 | /// The duration trip in horse. 75 | final double duration; 76 | 77 | /// Total distance travelled(in km) while exceeding speed limit over for 20 km/h or more. 78 | final double highOverSpeedMileage; 79 | 80 | /// Total distance travelled(in km) while exceeding speed limit over for 10 to 20 km/h. 81 | final double midOverSpeedMileage; 82 | 83 | /// The trip type has changed. 84 | final bool originChanged; 85 | 86 | /// The trip type. 87 | final String trackOriginCode; 88 | 89 | /// Track token. 90 | final String trackToken; 91 | 92 | /// Sharing type (ex: Shared). 93 | final String shareType; 94 | 95 | /// Start point city name. 96 | final String cityStart; 97 | 98 | /// End point city name. 99 | final String cityFinish; 100 | 101 | /// Array of track points 102 | final List points; 103 | final double dateUpdated; 104 | 105 | const TrackProcessed({ 106 | required this.startDate, 107 | required this.endDate, 108 | required this.addressStart, 109 | required this.addressEnd, 110 | required this.accelerationCount, 111 | required this.decelerationCount, 112 | required this.rating, 113 | required this.rating100, 114 | required this.ratingCornering, 115 | required this.ratingCornering100, 116 | required this.ratingAcceleration, 117 | required this.ratingAcceleration100, 118 | required this.ratingBraking, 119 | required this.ratingBraking100, 120 | required this.ratingSpeeding, 121 | required this.ratingSpeeding100, 122 | required this.ratingPhoneUsage, 123 | required this.ratingPhoneUsage100, 124 | required this.ratingTimeOfDay, 125 | required this.ratingReason, 126 | required this.addressStartParts, 127 | required this.addressFinishParts, 128 | required this.tags, 129 | required this.phoneUsage, 130 | required this.distance, 131 | required this.duration, 132 | required this.highOverSpeedMileage, 133 | required this.midOverSpeedMileage, 134 | required this.originChanged, 135 | required this.trackOriginCode, 136 | required this.trackToken, 137 | required this.shareType, 138 | required this.cityStart, 139 | required this.cityFinish, 140 | required this.points, 141 | required this.dateUpdated, 142 | }); 143 | 144 | factory TrackProcessed.fromJson(Map json) { 145 | final startDateString = json['startDate'] as String; 146 | final endDateString = json['endDate'] as String; 147 | final startDate = DateTime.parse(startDateString); //TO DO check 148 | final endDate = DateTime.parse(endDateString); //TO DO check 149 | final addressStart = json['addressStart'] as String; 150 | final addressEnd = json['addressEnd'] as String; 151 | final accelerationCount = json['accelerationCount'] as int; 152 | final decelerationCount = json['decelerationCount'] as int; 153 | final rating = json['rating'] as double; 154 | final rating100 = json['rating100'] as double; 155 | final ratingCornering = json['ratingCornering'] as double; 156 | final ratingCornering100 = json['ratingCornering100'] as double; 157 | final ratingAcceleration = json['ratingAcceleration'] as double; 158 | final ratingAcceleration100 = json['ratingAcceleration100'] as double; 159 | final ratingBraking = json['ratingBraking'] as double; 160 | final ratingBraking100 = json['ratingBraking100'] as double; 161 | final ratingSpeeding = json['ratingSpeeding'] as double; 162 | final ratingSpeeding100 = json['ratingSpeeding100'] as double; 163 | final ratingPhoneUsage = json['ratingPhoneUsage'] as double; 164 | final ratingPhoneUsage100 = json['ratingPhoneUsage100'] as double; 165 | final ratingTimeOfDay = json['ratingTimeOfDay'] as double; 166 | final ratingReason = json['ratingReason'] as String; 167 | final addressStartPartsJson = json['addressStartParts']; 168 | final addressStartParts = 169 | AddressParts.fromJson(addressStartPartsJson); //TO DO check 170 | final addressFinishPartsJson = json['addressFinishParts']; 171 | final addressFinishParts = 172 | AddressParts.fromJson(addressFinishPartsJson); //TO DO check 173 | final tagsList = json['tags'] as List; 174 | final tags = tagsList 175 | .map((json) => InnerTag.fromJson(json)) 176 | .toList(); //TO DO check 177 | final phoneUsage = json['phoneUsage'] as double; 178 | final distance = json['distance'] as double; 179 | final duration = json['duration'] as double; 180 | final highOverSpeedMileage = json['highOverSpeedMileage'] as double; 181 | final midOverSpeedMileage = json['midOverSpeedMileage'] as double; 182 | final originChanged = json['originChanged'] as bool; 183 | final trackOriginCode = json['trackOriginCode'] as String; 184 | final trackToken = json['trackToken'] as String; 185 | final shareType = json['shareType'] as String; 186 | final cityStart = json['cityStart'] as String; 187 | final cityFinish = json['cityFinish'] as String; 188 | final pointsList = json['points'] as List; 189 | final points = pointsList 190 | .map((json) => TrackPointProcessed.fromJson(json)) 191 | .toList(); //TO DO check 192 | final dateUpdated = json['dateUpdated'] as double; 193 | 194 | return TrackProcessed( 195 | startDate: startDate, 196 | endDate: endDate, 197 | addressStart: addressStart, 198 | addressEnd: addressEnd, 199 | accelerationCount: accelerationCount, 200 | decelerationCount: decelerationCount, 201 | rating: rating, 202 | rating100: rating100, 203 | ratingCornering: ratingCornering, 204 | ratingCornering100: ratingCornering100, 205 | ratingAcceleration: ratingAcceleration, 206 | ratingAcceleration100: ratingAcceleration100, 207 | ratingBraking: ratingBraking, 208 | ratingBraking100: ratingBraking100, 209 | ratingSpeeding: ratingSpeeding, 210 | ratingSpeeding100: ratingSpeeding100, 211 | ratingPhoneUsage: ratingPhoneUsage, 212 | ratingPhoneUsage100: ratingPhoneUsage100, 213 | ratingTimeOfDay: ratingTimeOfDay, 214 | ratingReason: ratingReason, 215 | addressStartParts: addressStartParts, 216 | addressFinishParts: addressFinishParts, 217 | tags: tags, 218 | phoneUsage: phoneUsage, 219 | distance: distance, 220 | duration: duration, 221 | highOverSpeedMileage: highOverSpeedMileage, 222 | midOverSpeedMileage: midOverSpeedMileage, 223 | originChanged: originChanged, 224 | trackOriginCode: trackOriginCode, 225 | trackToken: trackToken, 226 | shareType: shareType, 227 | cityStart: cityStart, 228 | cityFinish: cityFinish, 229 | points: points, 230 | dateUpdated: dateUpdated, 231 | ); 232 | } 233 | } 234 | 235 | class AddressParts { 236 | /// The country code of address. 237 | final String countryCode; 238 | 239 | /// The country of address. 240 | final String country; 241 | 242 | /// The county of address. 243 | final String county; 244 | 245 | /// The postal code of address. 246 | final String postalCode; 247 | 248 | /// The state of address. 249 | final String state; 250 | 251 | /// The city of address. 252 | final String city; 253 | 254 | /// The district of address. 255 | final String district; 256 | 257 | /// The street of address. 258 | final String street; 259 | 260 | /// The house of address. 261 | final String house; 262 | 263 | const AddressParts({ 264 | required this.countryCode, 265 | required this.country, 266 | required this.county, 267 | required this.postalCode, 268 | required this.state, 269 | required this.city, 270 | required this.district, 271 | required this.street, 272 | required this.house, 273 | }); 274 | 275 | factory AddressParts.fromJson(Map json) { 276 | final countryCode = json['countryCode'] as String; 277 | final country = json['country'] as String; 278 | final county = json['county'] as String; 279 | final postalCode = json['postalCode'] as String; 280 | final state = json['state'] as String; 281 | final city = json['city'] as String; 282 | final district = json['district'] as String; 283 | final street = json['street'] as String; 284 | final house = json['house'] as String; 285 | 286 | return AddressParts( 287 | countryCode: countryCode, 288 | country: country, 289 | county: county, 290 | postalCode: postalCode, 291 | state: state, 292 | city: city, 293 | district: district, 294 | street: street, 295 | house: house, 296 | ); 297 | } 298 | } 299 | 300 | enum SpeedType { normal, medium, high } 301 | 302 | enum AlertType { none, acceleration, deceleration } 303 | 304 | class TrackPointProcessed { 305 | final int number; 306 | final double totalMeters; 307 | final double speed; 308 | final double midSpeed; 309 | final DateTime pointDate; 310 | final double latitude; 311 | final double longitude; 312 | final double height; 313 | final double course; 314 | final double yaw; 315 | final double lateral; 316 | final AlertType alertType; 317 | final double alertValue; 318 | final SpeedType speedType; 319 | final double speedLimit; 320 | final double phoneUsage; 321 | final bool cornering; 322 | 323 | const TrackPointProcessed({ 324 | required this.number, 325 | required this.totalMeters, 326 | required this.speed, 327 | required this.midSpeed, 328 | required this.pointDate, 329 | required this.latitude, 330 | required this.longitude, 331 | required this.height, 332 | required this.course, 333 | required this.yaw, 334 | required this.lateral, 335 | required this.alertType, 336 | required this.alertValue, 337 | required this.speedType, 338 | required this.speedLimit, 339 | required this.phoneUsage, 340 | required this.cornering, 341 | }); 342 | 343 | factory TrackPointProcessed.fromJson(Map json) { 344 | final number = json['number'] as int; 345 | final totalMeters = json['totalMeters'] as double; 346 | final speed = json['speed'] as double; 347 | final midSpeed = json['midSpeed'] as double; 348 | final pointDateString = json['pointDate'] as String; //TO DO check 349 | final pointDate = DateTime.parse(pointDateString); 350 | final latitude = json['latitude'] as double; 351 | final longitude = json['longitude'] as double; 352 | final height = json['height'] as double; 353 | final course = json['course'] as double; 354 | final yaw = json['yaw'] as double; 355 | final lateral = json['lateral'] as double; 356 | 357 | final alertTypeString = json['alertType'] as String; //TO DO check 358 | final alertType = alertTypeFrom(alertTypeString); 359 | final alertValue = json['alertValue'] as double; 360 | final speedTypeString = json['speedType'] as String; //TO DO check 361 | final speedType = speedTypeFrom(speedTypeString); 362 | final speedLimit = json['speedLimit'] as double; 363 | final phoneUsage = json['phoneUsage'] as double; 364 | final cornering = json['cornering'] as bool; 365 | 366 | return TrackPointProcessed( 367 | number: number, 368 | totalMeters: totalMeters, 369 | speed: speed, 370 | midSpeed: midSpeed, 371 | pointDate: pointDate, 372 | latitude: latitude, 373 | longitude: longitude, 374 | height: height, 375 | course: course, 376 | yaw: yaw, 377 | lateral: lateral, 378 | alertType: alertType, 379 | alertValue: alertValue, 380 | speedType: speedType, 381 | speedLimit: speedLimit, 382 | phoneUsage: phoneUsage, 383 | cornering: cornering, 384 | ); 385 | } 386 | 387 | static SpeedType speedTypeFrom(String string) { 388 | if (string == 'normal') { 389 | return SpeedType.normal; 390 | } 391 | if (string == 'medium') { 392 | return SpeedType.medium; 393 | } 394 | if (string == 'high') { 395 | return SpeedType.high; 396 | } 397 | return SpeedType.normal; 398 | } 399 | 400 | static AlertType alertTypeFrom(String string) { 401 | if (string == 'none') { 402 | return AlertType.none; 403 | } 404 | if (string == 'acceleration') { 405 | return AlertType.acceleration; 406 | } 407 | if (string == 'deceleration') { 408 | return AlertType.deceleration; 409 | } 410 | return AlertType.none; 411 | } 412 | } 413 | 414 | class InnerTag { 415 | final String source; 416 | final String tag; 417 | final String type; 418 | final String timestamp; 419 | 420 | const InnerTag({ 421 | required this.source, 422 | required this.tag, 423 | required this.type, 424 | required this.timestamp, 425 | }); 426 | 427 | factory InnerTag.fromJson(Map json) { 428 | final source = json['source'] as String; 429 | final tag = json['tag'] as String; 430 | final type = json['type'] as String; 431 | final timestamp = json['timestamp'] as String; 432 | 433 | return InnerTag( 434 | source: source, 435 | tag: tag, 436 | type: type, 437 | timestamp: timestamp, 438 | ); 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /example/lib/title_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' show Platform; 3 | import 'package:uuid/uuid.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:telematics_sdk/telematics_sdk.dart'; 7 | 8 | const _sizedBoxSpace = SizedBox(height: 24); 9 | 10 | class TitleScreen extends StatefulWidget { 11 | TitleScreen({Key? key}) : super(key: key); 12 | 13 | @override 14 | _TitleScreenState createState() => _TitleScreenState(); 15 | } 16 | 17 | class _TitleScreenState extends State { 18 | final _trackingApi = TrackingApi(); 19 | late StreamSubscription 20 | _onPermissionWizardStateChanged; 21 | late StreamSubscription _onLowerPower; 22 | late StreamSubscription _onLocationChanged; 23 | 24 | var _deviceId = ''; 25 | var _isSdkEnabled = false; 26 | var _isAllRequiredPermissionsGranted = false; 27 | var _isTracking = true; 28 | var _isManualTracking = false; 29 | var _isAggressiveHeartbeats = false; 30 | TrackLocation? _location; 31 | 32 | final _tokenEditingController = TextEditingController(); 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _onPermissionWizardStateChanged = 38 | _trackingApi.onPermissionWizardClose.listen(_onPermissionWizardResult); 39 | _onLowerPower = _trackingApi.lowerPowerMode.listen(_onLowPowerResult); 40 | _onLocationChanged = _trackingApi.locationChanged.listen(_onLocationChangedResult); 41 | initPlatformState(); 42 | } 43 | 44 | // Platform messages are asynchronous, so we initialize in an async method. 45 | Future initPlatformState() async { 46 | final virtualDeviceToken = await _trackingApi.getDeviceId(); 47 | 48 | if (virtualDeviceToken != null && virtualDeviceToken.isNotEmpty) { 49 | _deviceId = virtualDeviceToken; 50 | _tokenEditingController.text = _deviceId; 51 | } else { 52 | await _trackingApi.setEnableSdk(enable: false); 53 | } 54 | 55 | _isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 56 | _isAllRequiredPermissionsGranted = 57 | await _trackingApi.isAllRequiredPermissionsAndSensorsGranted() ?? false; 58 | 59 | if (Platform.isIOS) { 60 | final disableTracking = await _trackingApi.isDisableTracking() ?? false; 61 | _isTracking = !disableTracking; 62 | _isAggressiveHeartbeats = await _trackingApi.isAggressiveHeartbeat() ?? false; 63 | } 64 | 65 | // If the widget was removed from the tree while the asynchronous platform 66 | // message was in flight, we want to discard the reply rather than calling 67 | // setState to update our non-existent appearance. 68 | if (!mounted) { 69 | return; 70 | } 71 | 72 | setState(() {}); 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Scaffold( 78 | appBar: AppBar( 79 | title: const Text('TelematicsSDK_demo'), 80 | ), 81 | body: ListView( 82 | shrinkWrap: false, 83 | padding: const EdgeInsets.symmetric(horizontal: 16), 84 | children: [ 85 | Text('SDK status: ${_isSdkEnabled ? 'Enabled' : 'Disabled'}'), 86 | Text( 87 | 'Permissions: ${_isAllRequiredPermissionsGranted ? 'Granted' : 'Not granted'}', 88 | ), 89 | (Platform.isIOS) ? Text('Tracking: ${_isSdkEnabled && _isTracking ? 'Enabled' : 'Disabled'}') : SizedBox.shrink(), 90 | Text('Manual Tracking: ${_isManualTracking ? 'Started' : 'Not stated'}'), 91 | Text(_getCurrentLocation()), 92 | TextFormField( 93 | controller: _tokenEditingController, 94 | decoration: const InputDecoration( 95 | labelText: 'Virtual device token', 96 | ), 97 | keyboardType: TextInputType.text, 98 | textInputAction: TextInputAction.go, 99 | maxLengthEnforcement: MaxLengthEnforcement.none, 100 | onFieldSubmitted: (token) { 101 | try { 102 | final uuid = Uuid.parse(token, validate: true); 103 | _onDeviceTokenUpdated(token: token); 104 | } on FormatException catch(e) { 105 | _tokenEditingController.text = _deviceId; 106 | _showSnackBar(e.message); 107 | } 108 | FocusScope.of(context).requestFocus(FocusNode()); 109 | }, 110 | ), 111 | Row( 112 | children: [ 113 | Expanded( 114 | child: ElevatedButton( 115 | onPressed: !_isSdkEnabled ? _onEnableSDK : null, 116 | child: const Text('Enable SDK'), 117 | ), 118 | ), 119 | SizedBox(width: 16), 120 | Expanded( 121 | child: ElevatedButton( 122 | onPressed: _isSdkEnabled ? _onDisableSDK : null, 123 | child: const Text('Disable SDK'), 124 | ), 125 | ), 126 | ], 127 | ), 128 | ElevatedButton( 129 | onPressed: _isSdkEnabled ? _onForceDisableSDK : null, 130 | child: const Text('Force Disable SDK with upload'), 131 | ), 132 | ElevatedButton( 133 | onPressed: !_isSdkEnabled && _deviceId.isNotEmpty ? _onLogout : null, 134 | child: const Text('Clear Device Token'), 135 | ), 136 | _sizedBoxSpace, 137 | ElevatedButton( 138 | onPressed: () async { 139 | await _onPermissionsSDK(); 140 | }, 141 | child: const Text('Start Permission Wizard'), 142 | ), 143 | _sizedBoxSpace, 144 | (Platform.isIOS) ? Row( 145 | children: [ 146 | Expanded( 147 | child: const Text('Aggressive Heartbeats'), 148 | ), 149 | Switch.adaptive( 150 | value: _isAggressiveHeartbeats, 151 | onChanged: _isSdkEnabled ? _onAggressiveHeartbeats : null 152 | ) 153 | ], 154 | ) : SizedBox.shrink(), 155 | (Platform.isIOS) ? Row( 156 | children: [ 157 | Expanded( 158 | child: ElevatedButton( 159 | onPressed: _isSdkEnabled && !_isTracking ? _onTrackingEnabled : null, 160 | child: const Text('Enable Tracking'), 161 | ), 162 | ), 163 | SizedBox(width: 16), 164 | Expanded( 165 | child: ElevatedButton( 166 | onPressed: _isSdkEnabled && _isTracking ? _onTrackingDisabled : null, 167 | child: const Text('Disable Tracking'), 168 | ), 169 | ), 170 | ], 171 | ) : SizedBox.shrink(), 172 | Row( 173 | children: [ 174 | Expanded( 175 | child: ElevatedButton( 176 | onPressed: !_isManualTracking ? _onStartManualTracking : null, 177 | child: const Text( 178 | 'Start tracking manually', 179 | textAlign: TextAlign.center, 180 | ), 181 | ), 182 | ), 183 | SizedBox(width: 16), 184 | Expanded( 185 | child: ElevatedButton( 186 | onPressed: !_isManualTracking ? _onStartPersistentManualTracking : null, 187 | child: const Text( 188 | 'Start persistent tracking manually', 189 | textAlign: TextAlign.center, 190 | ), 191 | ), 192 | ), 193 | SizedBox(width: 16), 194 | Expanded( 195 | child: ElevatedButton( 196 | onPressed: _isManualTracking ? _onStopManualTracking : null, 197 | child: const Text( 198 | 'Stop tracking manually', 199 | textAlign: TextAlign.center, 200 | ), 201 | ), 202 | ), 203 | ], 204 | ), 205 | ], 206 | ), 207 | ); 208 | } 209 | 210 | @override 211 | void dispose() { 212 | _onPermissionWizardStateChanged.cancel(); 213 | _onLowerPower.cancel(); 214 | _onLocationChanged.cancel(); 215 | super.dispose(); 216 | } 217 | 218 | Future _onDeviceTokenUpdated({required String token}) async { 219 | _deviceId = token; 220 | await _trackingApi.setDeviceID(deviceId: _deviceId); 221 | } 222 | 223 | Future _onEnableSDK() async { 224 | if (_deviceId.isEmpty) { 225 | _showSnackBar('virtual device token is empty'); 226 | } else if (!_isAllRequiredPermissionsGranted) { 227 | _showSnackBar('Please grant all required permissions'); 228 | } else { 229 | await _trackingApi.setEnableSdk(enable: true); 230 | 231 | _isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 232 | 233 | if (Platform.isIOS) { 234 | await _trackingApi.setDisableTracking(value: false); 235 | final disableTracking = await _trackingApi.isDisableTracking() ?? false; 236 | _isTracking = !disableTracking; 237 | } 238 | 239 | setState(() {}); 240 | } 241 | } 242 | 243 | Future _onDisableSDK() async { 244 | if (_isManualTracking) { 245 | await _trackingApi.stopManualTracking(); 246 | _isManualTracking = false; 247 | } 248 | 249 | if (Platform.isIOS) { 250 | await _trackingApi.setDisableTracking(value: true); 251 | final disableTracking = await _trackingApi.isDisableTracking() ?? false; 252 | _isTracking = !disableTracking; 253 | } 254 | 255 | await _trackingApi.setEnableSdk(enable: false); 256 | _isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 257 | setState(() {}); 258 | } 259 | 260 | Future _onForceDisableSDK() async { 261 | if (_isManualTracking) { 262 | await _trackingApi.stopManualTracking(); 263 | _isManualTracking = false; 264 | } 265 | 266 | if (Platform.isIOS) { 267 | await _trackingApi.setDisableTracking(value: true); 268 | final disableTracking = await _trackingApi.isDisableTracking() ?? false; 269 | _isTracking = !disableTracking; 270 | } 271 | 272 | await _trackingApi.setDisableWithUpload(); 273 | _isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 274 | setState(() {}); 275 | } 276 | 277 | Future _onLogout() async { 278 | _tokenEditingController.text = ''; 279 | _deviceId = ''; 280 | await _trackingApi.clearDeviceID(); 281 | setState(() {}); 282 | } 283 | 284 | Future _onAggressiveHeartbeats(bool value) async { 285 | await _trackingApi.setAggressiveHeartbeats(value: value); 286 | _isAggressiveHeartbeats = await _trackingApi.isAggressiveHeartbeat() ?? false; 287 | setState(() {}); 288 | } 289 | 290 | Future _onPermissionsSDK() async { 291 | if (!_isAllRequiredPermissionsGranted) { 292 | _trackingApi.showPermissionWizard( 293 | enableAggressivePermissionsWizard: false, 294 | enableAggressivePermissionsWizardPage: true, 295 | ); 296 | } else { 297 | _showSnackBar('All permissions are already granted'); 298 | } 299 | } 300 | 301 | void _onPermissionWizardResult(PermissionWizardResult result) { 302 | const _wizardResultMapping = { 303 | PermissionWizardResult.allGranted: 'All permissions was granted', 304 | PermissionWizardResult.notAllGranted: 'All permissions was not granted', 305 | PermissionWizardResult.canceled: 'Wizard cancelled', 306 | }; 307 | 308 | if (result == PermissionWizardResult.allGranted || 309 | result == PermissionWizardResult.notAllGranted) { 310 | setState(() { 311 | _isAllRequiredPermissionsGranted = 312 | result == PermissionWizardResult.allGranted; 313 | }); 314 | } 315 | 316 | _showSnackBar(_wizardResultMapping[result] ?? ''); 317 | } 318 | 319 | Future _onTrackingEnabled() async { 320 | if (!Platform.isIOS) { 321 | return; 322 | } 323 | final isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 324 | 325 | if (!isSdkEnabled) { 326 | _showSnackBar('Enable SDK first'); 327 | return; 328 | } 329 | 330 | await _trackingApi.setDisableTracking(value: false); 331 | final disableTracking = await _trackingApi.isDisableTracking() ?? false; 332 | 333 | setState(() { 334 | _isTracking = !disableTracking; 335 | }); 336 | } 337 | 338 | Future _onTrackingDisabled() async { 339 | if (!Platform.isIOS) { 340 | return; 341 | } 342 | if (_isManualTracking) { 343 | await _trackingApi.stopManualTracking(); 344 | } 345 | 346 | await _trackingApi.setDisableTracking(value: true); 347 | final disableTracking = await _trackingApi.isDisableTracking() ?? false; 348 | 349 | setState(() { 350 | _isTracking = !disableTracking; 351 | _isManualTracking = false; 352 | }); 353 | } 354 | 355 | Future _onStartManualTracking() async { 356 | final isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 357 | 358 | if (!isSdkEnabled) { 359 | _showSnackBar('Enable SDK first'); 360 | return; 361 | } 362 | 363 | if (!_isTracking && Platform.isIOS) { 364 | _showSnackBar('Enable tracking first'); 365 | return; 366 | } 367 | 368 | if (_isManualTracking) { 369 | _showSnackBar('Stop current track first'); 370 | return; 371 | } 372 | 373 | await _trackingApi.startManualTracking(); 374 | setState(() { 375 | _isManualTracking = true; 376 | }); 377 | } 378 | 379 | Future _onStartPersistentManualTracking() async { 380 | final isSdkEnabled = await _trackingApi.isSdkEnabled() ?? false; 381 | 382 | if (!isSdkEnabled) { 383 | _showSnackBar('Enable SDK first'); 384 | return; 385 | } 386 | 387 | if (!_isTracking && Platform.isIOS) { 388 | _showSnackBar('Enable tracking first'); 389 | return; 390 | } 391 | 392 | if (_isManualTracking) { 393 | _showSnackBar('Stop current track first'); 394 | return; 395 | } 396 | 397 | await _trackingApi.startManualPersistentTracking(); 398 | setState(() { 399 | _isManualTracking = true; 400 | }); 401 | } 402 | 403 | Future _onStopManualTracking() async { 404 | if (!_isManualTracking) { 405 | _showSnackBar('Start tracking first'); 406 | return; 407 | } 408 | 409 | await _trackingApi.stopManualTracking(); 410 | setState(() { 411 | _isManualTracking = false; 412 | }); 413 | } 414 | 415 | void _onLowPowerResult(bool isLowPower) { 416 | if (isLowPower) { 417 | _showSnackBar( 418 | "Low Power Mode.\nYour trips may be not recorded. Please, follow to Settings=>Battery=>Low Power", 419 | ); 420 | } 421 | } 422 | 423 | void _onLocationChangedResult(TrackLocation location) { 424 | print('location latitude: ${location.latitude}, longitude: ${location.longitude}'); 425 | setState(() { 426 | _location = location; 427 | }); 428 | } 429 | 430 | void _showSnackBar(String text) { 431 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 432 | ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text))); 433 | } 434 | 435 | String _getCurrentLocation() { 436 | if (_location != null) { 437 | return 'Location: ${_location!.latitude}, ${_location!.longitude}'; 438 | } else { 439 | return 'Location: null'; 440 | } 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | Telematics SDK Standard license Terms&Conditions 3 | 4 | 1. ACCEPTANCE 5 | 6 | These DataMotion Service Terms together with the Privacy Policy and all other additional terms and information that may be provided within the Service (collectively "Terms") govern your use of the DataMotion 's services ("Service") that include items as per follows: 7 | 8 | DataMotion telematics engine (Telematics SDK) that works with smartphone sensors ("Telematics SDK") 9 | Web-interface the SAAS platform ("DATA HUB") 10 | By registering for or using the Service or any portion of it you accept the Terms. 11 | 12 | The Terms constitute an agreement between you ("Licensee") and DataMotion Pte.Ltd, UEN 201918904Z, 68 CIRCULAR ROAD #02-01, SINGAPORE (049422), including its parents, subsidiaries, and affiliates (collectively "DataMotion"), defining your and DataMotion 's rights and responsibilities with respect to the Service. 13 | 14 | 2. LICENSE 15 | 16 | Subject to all terms hereof, DataMotion. hereby grants Licensee (i) a non-exclusive, non-transferable, revocable license to install and use the TELEMATICS SDK in object code solely for the purposes of integrating the TELEMATICS SDK into one Licensee mobile application ("Mobile Application") and to distribute the TELEMATICS SDK as part the Mobile Application and have such Mobile Application used by End Users, and (ii) a non-exclusive, non-transferable, revocable right to access through a web interface the DATA HUB that makes available information on End User routine behavior based on device data provided through the TELEMATICS SDK integrated in the Mobile Application installed on mobile devices of the End Users (such information and device data hereinafter jointly the "Data"). "End User" shall mean any person to which Licensee distributes its Mobile Application. A reference to the " TELEMATICS SDK" includes a reference to any part thereof and to any relating documentation. 17 | 18 | As part of the Service, DataMotion may provide you with certain software developed by DataMotion or, its licensors ("Software"). Your use of Software may be subject to separate terms and conditions that you must accept before using the Software. If there are no separate terms and conditions applicable to such Software, the following terms apply: DataMotion grants to you a limited, non-exclusive, non-transferable right to install and use the Software on your mobile device. For DataMotion software, applicable open source license terms apply. 19 | 20 | The Software maybe subject to export controls under your country regulations and other import or export control regulations. You agree to strictly comply with all applicable import and export regulations and acknowledge that you have the responsibility to obtain licenses to export, re-export, transfer, or import such Software. 21 | 22 | You are solely responsible for taking backup copies of the data you store on the Service. If the Service is discontinued or canceled, DataMotion may permanently delete your data. DataMotion has no obligation to return data to you after the Service is discontinued or canceled. 23 | 24 | 3. REGISTRATION 25 | 26 | To use a Service you may need to register and create a DATA HUB account with username and a password. You may need to provide us with certain personal and other information. HERE may verify your email address before account can be used. 27 | 28 | You agree to provide truthful and complete information when you register for the Service and to keep that information updated. You must take due care to protect your username and password against misuse by others and promptly notify DataMotion about any misuse. You, and your parent or legal guardian if you are a minor, are personally responsible for any use of the Service. 29 | 30 | 4. COPIES 31 | 32 | Licensee is authorized to duplicate the TELEMATICS SDK for internal test and development purposes and for distribution of the Mobile Application to End Users. Licensee is further allowed to make a reasonable number of copies of the TELEMATICS SDK for non-productive backup and recovery purposes. Any and all such copies shall in all respects be subject to the terms and conditions of this agreement. Licensee shall not make copies of the TELEMATICS SDK additional to those expressly permitted in this agreement nor copy any documentation accompanying the TELEMATICS SDK other than as strictly needed for its permitted use. Licensee shall not remove or obscure any copyright and/or trademark notices or other proprietary notices in the TELEMATICS SDK. All notices must be duplicated as they appear on the TELEMATICS SDK on all authorized copies. 33 | 34 | 35 | 5. USING THE SERVICE 36 | 37 | You agree to: 38 | 39 | Comply with applicable laws, the Terms and good manners; 40 | Respect the privacy of others; 41 | Not use any other technologies or initiate other activities that may harm the Service, or the interest or property of the Service users. 42 | Not to use any automated systems or means to access, acquire, copy or monitor any part of the service. 43 | DataMotion may but has no obligation to: 44 | Monitor or moderate any Content; 45 | Remove any Content from the Service; and 46 | Restrict access to any part of the Service at any time in its sole discretion. 47 | 6. RESTRICTIONS 48 | 49 | Licensee may combine the TELEMATICS SDK with its Mobile Application. Except to the extent expressly permitted by law, Licensee shall not otherwise modify, adapt, merge or create derivative works of the TELEMATICS SDK nor electronically transfer into another computer language, translate, reverse engineer or reengineer the TELEMATICS SDK. Licensee will not (i) disclose its DATA HUB credentials ("Credentials") to any third party; (ii) disclose the TELEMATICS SDK to any third party other than (x) its service providers that are under obligations in respect of the TELEMATICS SDK no less stringent than those set forth herein, or (y) End Users as an integrated part of the Mobile Application; (iii) attempt to access any systems, programs or data of DataMotion to which no access is granted hereunder or that are not required for your activities in connection with this agreement; or (iv) use any device or software to interfere or attempt to interfere with the proper operation of DATA HUB. Licensee will notify DataMotion immediately if it learns of any unauthorized use of its Credentials or unauthorized acquisition of Data. Licensee will not, and will ensure that its End Users shall not decompile, disassemble, analyze or examine the TELEMATICS SDK or otherwise attempt to learn the source code, structure, algorithms or ideas underlying the TELEMATICS SDK (except to the extent allowed by the applicable laws), e.g. for the purpose of reverse engineering, re-engineering or rebuilding an SDK with the same or similar functionalities. 50 | 51 | 7. INTELLECTUAL PROPERTY RIGHTS 52 | 53 | The TELEMATICS SDK and the DATA HUB are protected by applicable Singaporean and foreign laws and treaties, including copyright laws and treaty provisions. DataMotion and its third party licensors retain all title to, and, except as expressly and unambiguously licensed herein, all rights and interest in the TELEMATICS SDK and the Platform and all copies, versions, enhancements and derivative works thereof (other than the Licensee part of the Mobile Application) and all related documentation and materials, the DataMotion trademarks, trade names, icons and logos, and any and all intellectual property throughout the world in the foregoing. Except for the limited license granted herein, nothing herein shall be construed as DataMotion granting to Licensee or an End User any right, title or interest in or to the TELEMATICS SDK or the DATA HUB or any patent, trade secret or other intellectual property rights of DataMotion. All copies of the TELEMATICS SDK remain the property of DataMotion. The Data shall be owned by Licensee. DataMotion has the worldwide, non-exclusive, irrevocable, perpetual, royalty-free and fully paid-up right to use these Data in aggregated and anonymized form only for its internal business and marketing purposes, which include building statistical models and profiles. 54 | 55 | 8. FEEDBACK TO DATAMOTION 56 | 57 | By submitting any ideas, feedback and/or proposals ("Feedback") to DataMotion through the Service or other means, you acknowledge and agree that: (1) DataMotion may have similar development ideas to the Feedback; (2) your Feedback does not contain confidential or proprietary information of you or any third party; (3) DataMotion is not under any obligation of confidentiality with respect to the Feedback; (4) DataMotion may freely use, distribute, exploit and further develop and modify Feedback for any purpose; and (5) you are not entitled to any compensation of any kind from DataMotion. 58 | 59 | 9. NOTICES 60 | 61 | DataMotion may post notices within the Service. DataMotion may also send you notices about products and Services to the email address or telephone number you have provided to us. You are deemed to have received such notices at the latest within seven (7) days from DataMotion sending or posting those. Your continued use of the Services constitutes your receipt of all notices regardless of delivery method. 62 | 63 | 10. CONFIDENTIALITY 64 | 65 | Each party (the "Recipient") acknowledges that any information supplied by the other party (the "Provider") (including but not limited to the terms of this agreement and the TELEMATICS SDK) is confidential and undertakes to keep secret any such information until it enters the public domain through no fault of Recipient. Recipient shall not without Provider's prior written consent disclose the information to any third party, nor use the same for any purpose other than exercising its rights or performing its obligations under this agreement. Recipient shall take all steps necessary to prevent any of the information becoming known to unauthorized third parties other than its agents, consultants and advisors subject to such agents, consultants and advisors entering into confidentiality agreements no less restrictive than the provisions hereof. 66 | 67 | 11. FEES 68 | 69 | The Service offers subscriptions. You authorize the Service to place a periodical charge during the period of the subscription. The Service may also offer a trial period. If your Order involves a trial period (also known as try-and-buy), you may be charged when the trial period expires, unless you cancel in accordance with the subscription/trial terms. 70 | 71 | The prices in the Service may change from time to time. Prices include applicable taxes in effect at the time of your transaction, unless otherwise stated. There may be instances where you incur additional charges from your bank or credit card provider based on currency conversion rates used and/or additional fees assessed. DataMotion assumes no responsibility for the payment of bank or any other third party service fees or charges. 72 | 73 | Use of the Service may involve transmission of data through your service provider's network. Prices listed within the Service do not include possible data transmission, text message, voice or other service provision charges by your network service provider. 74 | DataMotion assumes no responsibility for the payment of any charges of your service providers. 75 | 76 | Payment of DataMotion 's invoices will be made within thirty (30) days of the invoice date by bank transfer in USD free of any deductions. All invoices shall be deemed accepted unless disputed in good faith within thirty (30) days after the invoice date. If an invoice is disputed, Fee shall pay the undisputed portion of the invoice when due. 77 | 78 | Any amount due but not paid in full on the due date shall automatically and without prior notice be increased with an interest for late payment of ten percent (10%) per year. This amount will, by way of indemnity, automatically and without prior notice be increased by ten percent (10%) with a minimum of one hundred USD ($100) from the day following the due date of the invoice, in addition to the principal amount and the interest for late payment. 79 | 80 | In addition to any other rights under this agreement, DataMotion shall have the right to suspend the use of the DATA HUB and connectivity of the TELEMATICS SDK in case of late payment. Fee will fully reimburse DataMotion the costs of notice, collection and recovery (including attorney's fees and expenses) made in connection with any unpaid invoice. DataMotion may increase the fees on an annual basis by giving thirty (30) days prior written notice to Licensee, provided that the annual percentage increase in the fees shall not exceed the aggregate percentage increase of the 10% per year 81 | 82 | 12. CANCELLATIONS AND REFUNDS 83 | 84 | You agree to the electronic delivery of Service being initiated concurrently with the receiving of the first data set from TELEMATICS SDK under your Company ID. You will not be able to cancel your Service once it has been initiated. The nature of the Service is such that it cannot be returned. 85 | 86 | In the event that after your Order you discover and promptly inform DataMotion within 48 hours that service you have ordered is faulty; (b) the Service delivers to you does not match the description of the Service you ordered via DataMotion or its parents, subsidiaries, affiliates or partners 87 | 88 | Please note that HERE may not be able to process your customer support request if you are unable to provide your Company ID, which is provided to you by DataMotion following your account registration. 89 | 90 | 13. TERMINATION 91 | 92 | This agreement enters into force on the Commencement Date and shall terminate on its one-year anniversary ("Initial Term"). This agreement shall automatically renew for successive one-year periods (each, a "Renewal Term") unless one party notifies the other in writing at least sixty (60) days prior to the end of the Initial Term or any Renewal Term that it does not wish to renew this agreement . This agreement and the rights granted to Licensee shall terminate on receipt of termination notice if Licensee fails to comply with any of the terms and conditions of this agreement (including non-payment within the time period specified in Section 4). Either party may terminate this agreement upon written notice in the event that the other party files a petition in bankruptcy or proceedings in bankruptcy are instituted against it, or any court assumes jurisdiction of such party and its assets pursuant to proceedings under any bankruptcy or reorganization act, or a receiver is appointed of that party's assets or that party makes an assignment for the benefit of its creditors. Upon termination, all licenses granted herein shall terminate, DataMotion may erase all Data, and Licensee shall immediately destroy all copies of the TELEMATICS SDK (including any documentation relating thereto) in its possession. Such termination shall be without prejudice to any other rights or remedies of DataMotion under this agreement or applicable law. Termination shall not relieve Licensee of its obligations which by their nature are intended to survive termination. 93 | 94 | 14. AVAILABILITY AND TECHNICAL REQUIREMENTS 95 | 96 | The availability of Service may vary and is subject to DataMotion 's sole discretion. DataMotion expressly disclaims any representation or warranty that any particular Service will be available. The Service may not be available in all countries and may be provided only in accordance with country regulations The Service, operations and some features may also be dependent on the network, compatibility of the devices used. 97 | 98 | To use the Service, you may need to use the latest version of TELEMATICS SDK 99 | 100 | DataMotion may, in its sole discretion, change, correct or discontinue the Service in whole or in part. The Service may not be available during maintenance breaks and other times. If DataMotion considers a Software update to be important or critical you may not continue using the previous version of the Software. DataMotion may prevent your use of the previous version of the Software or Service until you install the update. 101 | 102 | 15. PERSONAL DATA 103 | 104 | The Privacy Policy and any additional privacy information made available to you govern the use of your personal data. 105 | 106 | 16. INDEMNIFICATION 107 | 108 | You agree to defend and indemnify DataMotion from and against all third party claims and all liabilities, assessments, losses, costs or damages resulting from or arising out of (i) any lost data or other indirect or consequential damages of any character, including, without limitation, damages for loss of profits, loss of goodwill, work stoppage, device failure or malfunction, or any and all other commercial damages or losses, (ii) the cost of procuring substitute products, services or technology, (iii) any use or loss of Data by Licensee or by any third party who has obtained such Data (directly or indirectly) from Licensee, or (iv) any amounts in excess of the License Fees paid to DataMotion under this agreement during the six (6) months preceding the date the cause of action arose. Licensee assumes total responsibility and risk for its use of the TELEMATICS SDK and the Data and distribution of the Mobile Application. Licensee shall indemnify, defend and hold DataMotion harmless from and against all claims, suits, proceedings, awards, judgments, penalties, fines, damages, losses, liabilities, costs and expenses resulting from any and all third party (including End Users and public authorities) claims against DataMotion relating to any breach of this agreement by Licensee or the End Users or any use of the Data (or loss of Data) by Licensee or by any third party who has obtained such Data (directly or indirectly) from Licensee. As used in this section, " DataMotion " includes its employees, directors, officers, agents, representatives, subcontractors, service providers and suppliers. Claims for damages must be made by Licensee within six months of the incident to which they relate or be forever barred. 109 | 110 | 17. MISCELLANEOUS 111 | 112 | 17.1 Choice of Law 113 | 114 | The Terms are governed by the laws of Singapore 115 | 116 | 17.2 Validity 117 | 118 | The Terms neither exclude nor limit any of your mandatory rights in your country of residence that cannot by law be waived. If a provision of the Terms is found to be invalid, the remaining provisions will not be affected and the invalid provision will be replaced with a valid provision that comes closest to the result and purpose of the Terms. In the event one or more provisions of these Terms are not relevant to your use of the Service, it shall not impact the validity or enforceability of any other provision of the Terms or the Terms as a whole. If there is any conflict between these DataMotion Service Terms and the Privacy Policy, the provisions of these DataMotion Service Terms prevail. The provisions of the Terms that are intended to survive termination of your registration remain valid after termination. 119 | 120 | 17.3 Changes in Terms 121 | 122 | DataMotion may modify the Terms at any time without prior notice. If the Terms are changed in a material, adverse way, DataMotion will provide a separate notice advising of the change. 123 | 124 | You are responsible for regularly reviewing the Terms. Your continued use of the Service constitutes your consent to any changes and modification. 125 | 126 | 18. ASSIGNMENT 127 | 128 | DataMotion may assign its rights and obligations under these Terms to its corporate parent, its subsidiaries, or to any company under common control with DataMotion. Additionally, DataMotion may assign its rights and obligations under these Terms to a third party in connection with a merger, acquisition, sale of assets, by operation of law or otherwise. 129 | -------------------------------------------------------------------------------- /example/ios/Runner/London route 2.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | --------------------------------------------------------------------------------