diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml index c8466b860..8c9e68109 100644 --- a/.github/workflows/app.yml +++ b/.github/workflows/app.yml @@ -48,6 +48,11 @@ jobs: - run: flutter pub get - run: flutter pub run build_runner build - run: flutter build apk + env: + GPR_USER: ${{ secrets.GPR_USER }} + GPR_TOKEN: ${{ secrets.GPR_TOKEN }} + D4L_CLIENT_ID: ${{ secrets.D4L_CLIENT_ID }} + D4L_CLIENT_SECRET: ${{ secrets.D4L_CLIENT_SECRET }} build-ios: name: Build iOS Package diff --git a/.gitignore b/.gitignore index eb03e3e1e..0867c505d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules *.log +.idea/ \ No newline at end of file diff --git a/app/CONTRIBUTING.md b/app/CONTRIBUTING.md index 93c6e12ce..4635d8c4a 100644 --- a/app/CONTRIBUTING.md +++ b/app/CONTRIBUTING.md @@ -16,6 +16,27 @@ Please also see the [contribution guide in the root folder](../CONTRIBUTING.md). You should now be able to run the app by opening the debug panel on the left and pressing the green triangle at the top (or using the shortcut F5). +### `secrets.properties` + +The CHDP libraries require a few secrets to build. In +[`android/secrets.properties`](android/secrets.properties) (gitignored) add the +following keys and appropriate values: + +```properties +gpr.user= +gpr.token= + +d4l.clientId= +d4l.clientSecret= +``` + +The first two are a Github username and Personal Access Token with the +`read:packages` scope. This is used by gradle to download the SDK from D4L's +private repositories. + +The next two are secrets given by D4L. Ask Thomas at Thomas.Harris (at) hpi.de +for them. + ## Architecture The app consists of multiple so-called modules. Our main modules correspond to diff --git a/app/android/.gitignore b/app/android/.gitignore index 6f568019d..77131d6a4 100644 --- a/app/android/.gitignore +++ b/app/android/.gitignore @@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java key.properties **/*.keystore **/*.jks +secrets.properties \ No newline at end of file diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index 2cc5d04b6..ea7f7c3c2 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -25,6 +25,21 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +def secrets = new Properties() +def secretsPropertiesFile = new File(rootProject.projectDir, "secrets.properties") +if (secretsPropertiesFile.isFile()) { + secretsPropertiesFile.withReader("UTF-8") { reader -> secrets.load(reader) } +} else { + assert System.getenv('GPR_USER') + assert System.getenv('GPR_TOKEN') + assert System.getenv('D4L_CLIENT_ID') + assert System.getenv('D4L_CLIENT_SECRET') + secrets.setProperty("gpr.user", System.getenv('GPR_USER')) + secrets.setProperty("gpr.token", System.getenv('GPR_TOKEN')) + secrets.setProperty("d4l.clientId", System.getenv('D4L_CLIENT_ID')) + secrets.setProperty("d4l.clientSecret", System.getenv('D4L_CLIENT_SECRET')) +} + android { compileSdkVersion 31 @@ -44,13 +59,27 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "de.hpi.pharme" - minSdkVersion 19 + minSdkVersion 23 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName + + manifestPlaceholders += [ + platform : "s4h", + environment : "staging", + clientId : secrets.getProperty("d4l.clientId"), + clientSecret : secrets.getProperty("d4l.clientSecret"), + redirectScheme: "eu.smart4health.7cf2befc-a010-4290-996c-a94426b5ce65", + debug : "false" + ] } buildTypes { + + debug { + matchingFallbacks = ["release", "debug"] + } + release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. @@ -65,4 +94,8 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "care.data4life.hc-sdk-kmp:sdk-android:1.15.1" + implementation "care.data4life.hc-fhir-sdk-java:fhir-java:1.6.2" + implementation "com.jakewharton.threetenabp:threetenabp:1.4.0" } diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index d1df7c5b4..000000000 --- a/app/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index af3e70696..acc0b29d3 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,9 @@ + when (call.method) { + "isLoggedIn" -> Data4LifeClient.getInstance().isUserLoggedIn( + object : ResultListener { + override fun onSuccess(t: Boolean) = result.success(t) + + override fun onError(exception: D4LException) = + result.error( + "D4LException", + "Failed to check login status", + exception + ) + } + ) + "login" -> { + val intent = Data4LifeClient.getInstance().getLoginIntent( + this@MainActivity, + setOf( + "perm:r", + "rec:r", + "rec:w", + "attachment:r", + "attachment:w", + "user:r", + "user:w", + "user:q", + ) + ) + + this@MainActivity.startActivityForResult(intent, Data4LifeClient.D4L_AUTH) + + result.success(0) + } + "logout" -> { + Data4LifeClient.getInstance().logout( + object : care.data4life.sdk.listener.Callback { + override fun onSuccess() = result.success(0) + + override fun onError(exception: D4LException) = + result.error( + "D4LException", + "Failed to logout", + exception + ) + } + ) + + } + "upload" -> { + val path: String = call.argument("path")!! + val title: String = call.argument("title")!! + val docRef = makeDocumentReference(path, title) + + Data4LifeClient.getInstance().fhir4.create( + resource = docRef, + annotations = listOf("pharme"), + callback = object : + care.data4life.sdk.call.Callback> { + override fun onSuccess(_result: Fhir4Record) { + result.success(true) + } + + override fun onError(exception: D4LException) { + result.success(false) + } + } + ) + } + "toast" -> { + val msg: String = call.argument("msg")!! + + android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show() + } + else -> result.notImplemented() + } + } + } + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: android.content.Intent?, + ) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == Data4LifeClient.D4L_AUTH) { + if (resultCode == android.app.Activity.RESULT_OK) { + android.util.Log.i("PHARME", "login successful") + } + } + } } + +fun makeDocumentReference(path: String, title: String): DocumentReference { + val bytes = File(path).readBytes() + return DocumentReference( + CodeSystemDocumentReferenceStatus.CURRENT, + listOf( + DocumentReference.DocumentReferenceContent( + Attachment().apply { + contentType = "application/pdf" + data = Base64.encodeToString( + bytes, + Base64.NO_WRAP, + ) + size = bytes.size + hash = with(MessageDigest.getInstance("SHA-1")) { + Base64.encodeToString(digest(bytes), Base64.NO_WRAP) + } + }, + ), + ), + ).apply { + description = title + date = FhirDateTimeConverter.toFhirInstant(Calendar.getInstance().time) + } +} \ No newline at end of file diff --git a/app/android/app/src/main/res/values-night/styles.xml b/app/android/app/src/main/res/values-night/styles.xml index 449a9f930..343700c66 100644 --- a/app/android/app/src/main/res/values-night/styles.xml +++ b/app/android/app/src/main/res/values-night/styles.xml @@ -1,7 +1,7 @@ - diff --git a/app/android/app/src/main/res/values/styles.xml b/app/android/app/src/main/res/values/styles.xml index d74aa35c2..0ad2b7ea7 100644 --- a/app/android/app/src/main/res/values/styles.xml +++ b/app/android/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - diff --git a/app/android/build.gradle b/app/android/build.gradle index 76374445e..a481163f5 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -11,10 +11,50 @@ buildscript { } } +def secrets = new Properties() +def secretsPropertiesFile = new File(rootProject.projectDir, "secrets.properties") +if (secretsPropertiesFile.isFile()) { + secretsPropertiesFile.withReader("UTF-8") { reader -> secrets.load(reader) } +} else { + assert System.getenv('GPR_USER') + assert System.getenv('GPR_TOKEN') + assert System.getenv('D4L_CLIENT_ID') + assert System.getenv('D4L_CLIENT_SECRET') + secrets.setProperty("gpr.user", System.getenv('GPR_USER')) + secrets.setProperty("gpr.token", System.getenv('GPR_TOKEN')) + secrets.setProperty("d4l.clientId", System.getenv('D4L_CLIENT_ID')) + secrets.setProperty("d4l.clientSecret", System.getenv('D4L_CLIENT_SECRET')) +} + allprojects { repositories { google() mavenCentral() + + // tried to loop over a list of urls like in kotlin but no dice + maven { + url "https://maven.pkg.github.com/d4l-data4life/hc-util-sdk-kmp" + credentials { + username secrets.getProperty("gpr.user") + password secrets.getProperty("gpr.token") + } + } + + maven { + url "https://maven.pkg.github.com/d4l-data4life/hc-fhir-sdk-java" + credentials { + username secrets.getProperty("gpr.user") + password secrets.getProperty("gpr.token") + } + } + + maven { + url "https://maven.pkg.github.com/d4l-data4life/hc-fhir-helper-sdk-kmp" + credentials { + username secrets.getProperty("gpr.user") + password secrets.getProperty("gpr.token") + } + } } } diff --git a/app/lib/common/models/medication/guideline.dart b/app/lib/common/models/medication/guideline.dart index 75415227c..c7d6c34f9 100644 --- a/app/lib/common/models/medication/guideline.dart +++ b/app/lib/common/models/medication/guideline.dart @@ -1,8 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive/hive.dart'; -import '../../module.dart'; - part 'guideline.g.dart'; @HiveType(typeId: 9) @@ -70,7 +68,7 @@ class Guideline { @override int get hashCode { - return hashValues( + return Object.hash( implication, recommendation, warningLevel, @@ -115,7 +113,7 @@ class Phenotype { } @override - int get hashCode => hashValues(geneResult.name, geneSymbol.name); + int get hashCode => Object.hash(geneResult.name, geneSymbol.name); } @HiveType(typeId: 11) diff --git a/app/lib/common/models/medication/medication.dart b/app/lib/common/models/medication/medication.dart index 3cae5d3f6..5092f4abd 100644 --- a/app/lib/common/models/medication/medication.dart +++ b/app/lib/common/models/medication/medication.dart @@ -77,7 +77,7 @@ class MedicationWithGuidelines { guidelines.contentEquals(other.guidelines); @override - int get hashCode => hashValues(name, guidelines); + int get hashCode => Object.hash(name, guidelines); } List medicationsFromHTTPResponse(Response resp) { diff --git a/app/lib/common/pages/medications/medication.dart b/app/lib/common/pages/medications/medication.dart index 236942aef..94b4e384a 100644 --- a/app/lib/common/pages/medications/medication.dart +++ b/app/lib/common/pages/medications/medication.dart @@ -1,13 +1,11 @@ // ignore_for_file: avoid_returning_null_for_void -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'dart:io'; -import '../../l10n.dart'; -import '../../models/module.dart'; -import '../../theme.dart'; +import 'package:flutter/services.dart'; + +import '../../../common/module.dart'; import '../../utilities/pdf_utils.dart'; -import '../../widgets/module.dart'; import 'cubit.dart'; import 'widgets/module.dart'; @@ -50,7 +48,7 @@ class MedicationPage extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(medication), + _buildHeader(medication, context), SizedBox(height: 20), Disclaimer(), SizedBox(height: 20), @@ -67,7 +65,8 @@ class MedicationPage extends StatelessWidget { ); } - Widget _buildHeader(MedicationWithGuidelines medication) { + Widget _buildHeader( + MedicationWithGuidelines medication, BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -75,13 +74,51 @@ class MedicationPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(medication.name, style: PharMeTheme.textTheme.displaySmall), - IconButton( - onPressed: () => sharePdf(medication), - icon: Icon( - Icons.ios_share, - size: 32, - color: PharMeTheme.primaryColor, - ), + Row( + children: [ + if (Platform.isAndroid) + IconButton( + onPressed: () async { + final isLoggedIn = await MethodChannel('chdp') + .invokeMethod('isLoggedIn'); + + if (isLoggedIn) { + await showDialog( + context: context, + builder: (_) => + _shouldUploadDialog(context, () async { + final isSuccess = + await sharePdfSmart4Health(medication); + if (isSuccess) { + await MethodChannel('chdp').invokeMethod( + 'toast', + {'msg': 'Upload successful'}); + } else { + await MethodChannel('chdp').invokeMethod( + 'toast', {'msg': 'Upload failed'}); + } + })); + } else { + await showDialog( + context: context, + builder: (_) => _pleaseLogInDialog(context)); + } + }, + icon: Icon( + Icons.upload_file, + size: 32, + color: PharMeTheme.primaryColor, + )) + else + null, + IconButton( + onPressed: () => sharePdf(medication), + icon: Icon( + Platform.isAndroid ? Icons.share : Icons.ios_share, + size: 32, + color: PharMeTheme.primaryColor, + )), + ].whereType().toList(), ), ], ), @@ -102,4 +139,36 @@ class MedicationPage extends StatelessWidget { ], ); } + + Widget _pleaseLogInDialog(BuildContext context) { + return AlertDialog( + title: Text('Please log in'), + content: Text( + 'To upload your report, please log in to Smart4Health under More > Account Settings'), + actions: [ + TextButton( + onPressed: context.router.root.pop, + child: Text('Okay'), + ), + ], + ); + } + + // intellij autoformatter having a normal one + Widget _shouldUploadDialog( + BuildContext context, Future Function() onPositivePressed) { + return AlertDialog( + title: Text('Upload report'), + content: Text('Would you like to upload your report to Smart4Health?'), + actions: [ + TextButton(onPressed: context.router.root.pop, child: Text('Cancel')), + TextButton( + onPressed: () async { + await context.router.root.pop(); + await onPositivePressed(); + }, + child: Text('Upload'), + ) + ]); + } } diff --git a/app/lib/common/utilities/hive_utils.dart b/app/lib/common/utilities/hive_utils.dart index 9abc3a5f8..fbb3433fc 100644 --- a/app/lib/common/utilities/hive_utils.dart +++ b/app/lib/common/utilities/hive_utils.dart @@ -1,19 +1,17 @@ -import 'dart:convert'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:hive/hive.dart'; +// import 'package:flutter_secure_storage/flutter_secure_storage.dart'; Future> retrieveExistingOrGenerateKey() async { - const secureStorage = FlutterSecureStorage(); + return List.filled(32, 0); + // const secureStorage = FlutterSecureStorage(); // if key not exists return null - final encryprionKey = await secureStorage.read(key: 'key'); - if (encryprionKey == null) { - final key = Hive.generateSecureKey(); - await secureStorage.write( - key: 'key', - value: base64UrlEncode(key), - ); - } - final key = await secureStorage.read(key: 'key'); - return base64Url.decode(key!); + // final encryprionKey = await secureStorage.read(key: 'key'); + // if (encryprionKey == null) { + // final key = Hive.generateSecureKey(); + // await secureStorage.write( + // key: 'key', + // value: base64UrlEncode(key), + // ); + // } + // final key = Hive.generateSecureKey();// await secureStorage.read(key: 'key'); + // return key; // base64Url.decode(key); } diff --git a/app/lib/common/utilities/pdf_utils.dart b/app/lib/common/utilities/pdf_utils.dart index 4c11cfdd2..3dd18cd2e 100644 --- a/app/lib/common/utilities/pdf_utils.dart +++ b/app/lib/common/utilities/pdf_utils.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/services.dart'; import 'package:flutter_share/flutter_share.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pdf/pdf.dart'; @@ -30,6 +31,14 @@ Future sharePdf(MedicationWithGuidelines medication) async { await FlutterShare.shareFile(title: medication.name, filePath: path); } +Future sharePdfSmart4Health(MedicationWithGuidelines medication) async { + final path = await createPdf(medication); + return await MethodChannel('chdp').invokeMethod('upload', { + 'path': path, + 'title': 'PharMe Report: ${medication.name}' + }); +} + pw.Widget buildPdfPage( pw.Context context, MedicationWithGuidelines medication, @@ -140,6 +149,7 @@ class _PdfSegment extends pw.StatelessWidget { _PdfSegment({required this.child}); final pw.Widget child; + @override pw.Widget build(Context context) { return pw.SizedBox( @@ -154,6 +164,7 @@ class _PdfText extends pw.StatelessWidget { final String title; final String? text; + @override pw.Widget build(Context context) { return pw.RichText( diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index bc1326ee5..d4c89456f 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -68,6 +68,7 @@ "settings_page_continue": "Continue", "settings_page_delete_data": "Delete App Data", "settings_page_delete_data_text": "Are you sure that you want to delete all app data? This also includes your genomic data.", + "settings_page_login_smart4health": "Log in to Smart4Health", "settings_page_more": "More", "settings_page_onboarding": "Onboarding", "settings_page_about_us": "About us", diff --git a/app/lib/reports/pages/cubit.dart b/app/lib/reports/pages/cubit.dart index d6fbfce42..558862654 100644 --- a/app/lib/reports/pages/cubit.dart +++ b/app/lib/reports/pages/cubit.dart @@ -72,6 +72,8 @@ class ReportsCubit extends Cubit { class ReportsState with _$ReportsState { const factory ReportsState.initial() = _InitialState; const factory ReportsState.loading() = _LoadingState; + // add a member here that indicates showing the s4h button, load in the cubit by asking android + // button then talks to android again const factory ReportsState.loaded( List medications, ) = _LoadedState; diff --git a/app/lib/settings/chdp_state.dart b/app/lib/settings/chdp_state.dart new file mode 100644 index 000000000..8f4833829 --- /dev/null +++ b/app/lib/settings/chdp_state.dart @@ -0,0 +1,84 @@ +import 'package:flutter/services.dart'; + +import '../common/module.dart'; + +class ChdpListTile extends StatefulWidget { + const ChdpListTile({Key? key}) : super(key: key); + + @override + State createState() => _ChdpState(); +} + +class _ChdpState extends State with WidgetsBindingObserver { + static const platform = MethodChannel('chdp'); + + bool _isLoggedIn = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + getIsLoggedIn().whenComplete(() => null); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + getIsLoggedIn().whenComplete(() => null); + } + } + + @override + Widget build(BuildContext context) { + final title = _isLoggedIn + ? Text('Log out of Smart4Health') + : Text('Log in to Smart4Health'); + + return ListTile( + title: title, + trailing: _isLoggedIn ? null : Icon(Icons.chevron_right), + onTap: () => { + // are you okay intellij autoformatter???? + if (_isLoggedIn) + {logout().whenComplete(getIsLoggedIn)} + else + {login().whenComplete(() => null)} + }, + ); // ListTile + } + + Future getIsLoggedIn() async { + var isLoggedIn = false; + try { + isLoggedIn = await platform.invokeMethod('isLoggedIn'); + } on PlatformException catch (e) { + debugPrint('platform exception in getIsLoggedIn: ${e.toString()}'); + } + + setState(() { + _isLoggedIn = isLoggedIn; + }); + } + + Future login() async { + try { + await platform.invokeMethod('login'); + } on PlatformException catch (e) { + debugPrint('platform exception in login: ${e.toString()}'); + } + } + + Future logout() async { + try { + await platform.invokeMethod('logout'); + } on PlatformException catch (e) { + debugPrint('platform exception in logout: ${e.toString()}'); + } + } +} diff --git a/app/lib/settings/pages/settings.dart b/app/lib/settings/pages/settings.dart index 023f41dd1..241ef2a9b 100644 --- a/app/lib/settings/pages/settings.dart +++ b/app/lib/settings/pages/settings.dart @@ -1,12 +1,21 @@ +import 'dart:io'; + import '../../common/module.dart'; +import '../chdp_state.dart'; import '../utils.dart'; -class SettingsPage extends StatelessWidget { +class SettingsPage extends StatefulWidget { const SettingsPage({Key? key}) : super(key: key); + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { @override Widget build(BuildContext context) { - return ListView(children: [ + return ListView( + children: [ ListTile( title: Text( context.l10n.settings_page_account_settings, @@ -21,6 +30,10 @@ class SettingsPage extends StatelessWidget { builder: (_) => _deleteDataDialog(context), ), ), + if (Platform.isAndroid) + ChdpListTile() + else + null, Divider(), ListTile( title: Text( @@ -48,7 +61,7 @@ class SettingsPage extends StatelessWidget { trailing: Icon(Icons.chevron_right), onTap: () => context.router.push(TermsAndConditionsRoute()), ), - ]); + ].whereType().toList()); } Widget _deleteDataDialog(BuildContext context) { diff --git a/app/pubspec.lock b/app/pubspec.lock index e013a01e7..4c91c92de 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -21,7 +21,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.3.0" + version: "3.1.11" args: dependency: transitive description: @@ -78,6 +78,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.0.3" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.3" boolean_selector: dependency: transitive description: @@ -190,13 +197,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.2" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.0.1" csslib: dependency: transitive description: @@ -281,6 +295,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" dropdown_button2: dependency: "direct main" description: @@ -335,6 +356,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.0.1" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_hooks: dependency: "direct main" description: @@ -361,48 +387,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_secure_storage: - dependency: "direct main" - description: - name: flutter_secure_storage - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.2" - flutter_secure_storage_linux: - dependency: transitive - description: - name: flutter_secure_storage_linux - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - flutter_secure_storage_platform_interface: - dependency: transitive - description: - name: flutter_secure_storage_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_secure_storage_web: - dependency: transitive - description: - name: flutter_secure_storage_web - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - flutter_secure_storage_windows: - dependency: transitive - description: - name: flutter_secure_storage_windows - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" flutter_share: dependency: "direct main" description: @@ -462,6 +446,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" functions_client: dependency: transitive description: @@ -560,6 +549,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -651,6 +645,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + mocktail: + dependency: "direct dev" + description: + name: mocktail + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" nested: dependency: transitive description: @@ -658,6 +659,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" package_config: dependency: transitive description: @@ -959,6 +967,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" shelf_web_socket: dependency: transitive description: @@ -985,6 +1007,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.2" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10" source_span: dependency: transitive description: @@ -1034,6 +1070,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.6" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" term_glyph: dependency: transitive description: @@ -1041,6 +1084,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + test: + dependency: transitive + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.21.1" test_api: dependency: transitive description: @@ -1048,6 +1098,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.9" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.13" time: dependency: transitive description: @@ -1068,7 +1125,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.3.0" universal_html: dependency: transitive description: @@ -1146,6 +1203,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "8.2.2" watcher: dependency: transitive description: @@ -1160,6 +1224,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" win32: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 10a429803..3745dba47 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: flutter_hooks: ^0.18.5+1 flutter_localizations: sdk: flutter - flutter_secure_storage: ^5.0.2 +# flutter_secure_storage: ^5.0.2 flutter_share: ^2.0.0 flutter_sliding_up_panel: ^2.0.1 flutter_svg: ^1.1.1