From 9588520ffb0e87f0d4f34756c374679b4e94136b Mon Sep 17 00:00:00 2001 From: derdilla Date: Thu, 12 Sep 2024 19:50:01 +0200 Subject: [PATCH 1/3] Fix test failure in data package (#431) --- .../lib/src/repositories/bodyweight_repository_impl.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/health_data_store/lib/src/repositories/bodyweight_repository_impl.dart b/health_data_store/lib/src/repositories/bodyweight_repository_impl.dart index b72c08a1..128fc366 100644 --- a/health_data_store/lib/src/repositories/bodyweight_repository_impl.dart +++ b/health_data_store/lib/src/repositories/bodyweight_repository_impl.dart @@ -4,7 +4,6 @@ import 'package:health_data_store/health_data_store.dart'; import 'package:health_data_store/src/database_helper.dart'; import 'package:health_data_store/src/database_manager.dart'; import 'package:health_data_store/src/extensions/datetime_seconds.dart'; -import 'package:health_data_store/src/repositories/bodyweight_repository.dart'; import 'package:sqflite_common/sqflite.dart'; /// Implementation of repository for [BodyweightRecord]s. @@ -40,7 +39,7 @@ class BodyweightRepositoryImpl extends BodyweightRepository { final results = await _db.rawQuery( 'SELECT timestampUnixS, weightKg ' 'FROM Timestamps AS t ' - 'RIGHT JOIN Weight AS w ON t.entryID = w.entryID ' + 'INNER JOIN Weight AS w ON t.entryID = w.entryID ' 'WHERE timestampUnixS BETWEEN ? AND ?', [range.startStamp, range.endStamp] ); From 256c7cf557a95ecdc64987d24ddbaa871fea077d Mon Sep 17 00:00:00 2001 From: derdilla Date: Fri, 20 Sep 2024 08:18:07 +0200 Subject: [PATCH 2/3] Build debug apks in PRs (#442) * Cache generated files * Update app-CI.yml * Build apk on labeling * update android gradle plugin version * replace jSaver * ensure no irregular save attempt is made * Remove unneccessary Dependency * Fix workflow caching * Remove unused workflow --- .github/workflows/app-CI.yml | 27 +++++++-- .github/workflows/coverage-overview.yml | 45 -------------- .github/workflows/pr.yml | 58 +++++++++++++++++++ .../gradle/wrapper/gradle-wrapper.properties | 4 +- app/android/settings.gradle | 2 +- .../export_import/export_button_bar.dart | 29 +++------- .../settings/export_import_screen.dart | 31 +++++----- app/pubspec.lock | 30 +++++----- app/pubspec.yaml | 6 +- 9 files changed, 125 insertions(+), 107 deletions(-) delete mode 100644 .github/workflows/coverage-overview.yml create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/app-CI.yml b/.github/workflows/app-CI.yml index d553862c..02914465 100644 --- a/.github/workflows/app-CI.yml +++ b/.github/workflows/app-CI.yml @@ -31,17 +31,26 @@ jobs: sparse-checkout: | app health_data_store + - name: Cache generated health data store + id: cache-generated + uses: actions/cache@v4 + with: + path: health_data_store/lib + key: builder-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }} - name: Setup dart + if: steps.cache-generated.outputs.cache-hit != 'true' uses: dart-lang/setup-dart@v1 with: sdk: ${{ env.DART_SDK }} - name: Generate code + if: steps.cache-generated.outputs.cache-hit != 'true' run: dart run build_runner build working-directory: health_data_store - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: ${{ env.FLUTTER_CHANNEL }} + cache: true - name: Disable analytics run: flutter config --no-analytics - name: Generate mock code @@ -144,23 +153,31 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - - name: Setup Java - uses: actions/setup-java@v3 with: - distribution: 'zulu' - java-version: ${{ env.JAVA_VERSION }} - + # ensures there are no unexpected directories needed + sparse-checkout: | + app + health_data_store + - name: Cache generated health data store + id: cache-generated + uses: actions/cache@v4 + with: + path: health_data_store/lib + key: builder-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }} - name: Setup dart + if: steps.cache-generated.outputs.cache-hit != 'true' uses: dart-lang/setup-dart@v1 with: sdk: ${{ env.DART_SDK }} - name: Generate code + if: steps.cache-generated.outputs.cache-hit != 'true' run: dart run build_runner build working-directory: health_data_store - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: ${{ env.FLUTTER_CHANNEL }} + cache: true - name: Disable analytics run: flutter config --no-analytics - name: Build apk diff --git a/.github/workflows/coverage-overview.yml b/.github/workflows/coverage-overview.yml deleted file mode 100644 index 0b012c38..00000000 --- a/.github/workflows/coverage-overview.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Generate and Deploy Coverage Report - -on: - workflow_run: - workflows: - - '📱 Application' - - '📦 Packages' - types: - - completed - -jobs: - generate-deploy-coverage: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Download app coverage - uses: actions/download-artifact@v3 - with: - name: app-coverage - path: coverage/app - - - name: Download health data store coverage - uses: actions/download-artifact@v3 - with: - name: health_data_store-coverage - path: coverage/health_data_store - - - name: Combine Coverage Reports - run: | - mkdir -p coverage/combined - cat coverage/app/lcov.info coverage/health_data_store/coverage.lcov > coverage/combined/lcov.info - - - name: Generate HTML Report - run: | - npm install -g lcov-report - lcov-report -i coverage/combined/lcov.info -o coverage/combined - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: coverage/combined \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..ea38c972 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,58 @@ +name: PRs + +on: + pull_request: + types: [ labeled ] + +env: + FLUTTER_CHANNEL: 'beta' + DART_SDK: 'beta' + +permissions: + pull-requests: write + +jobs: + build: + if: ${{ github.event.label.name == 'build-apk' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + sparse-checkout: | + app + health_data_store + - name: Cache generated health data store + id: cache-generated + uses: actions/cache@v4 + with: + path: health_data_store/lib + key: builder-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }} + - name: Setup dart + if: steps.cache-generated.outputs.cache-hit != 'true' + uses: dart-lang/setup-dart@v1 + with: + sdk: ${{ env.DART_SDK }} + - name: Generate code + if: steps.cache-generated.outputs.cache-hit != 'true' + run: dart run build_runner build + working-directory: health_data_store + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: ${{ env.FLUTTER_CHANNEL }} + cache: true + - name: Disable analytics + run: flutter config --no-analytics + - name: Build apk + run: flutter build apk --flavor github --debug + working-directory: app + - uses: actions/upload-artifact@v4 + with: + name: build-results + path: app/build/app/outputs/flutter-apk + - uses: mondeja/remove-labels-gh-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: build-apk diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties index 43ec40bc..1eb4e49d 100644 --- a/app/android/gradle/wrapper/gradle-wrapper.properties +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip -distributionSha256Sum=97a52d145762adc241bad7fd18289bf7f6801e08ece6badf80402fe2b9f250b1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip +distributionSha256Sum=f30b29580fe11719087d698da23f3b0f0d04031d8995f7dd8275a31f7674dc01 diff --git a/app/android/settings.gradle b/app/android/settings.gradle index 615b56a7..db7675cc 100644 --- a/app/android/settings.gradle +++ b/app/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.4.2" apply false + id "com.android.application" version "8.1.0" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/app/lib/features/export_import/export_button_bar.dart b/app/lib/features/export_import/export_button_bar.dart index 733d4cb2..708e41cc 100644 --- a/app/lib/features/export_import/export_button_bar.dart +++ b/app/lib/features/export_import/export_button_bar.dart @@ -22,8 +22,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:health_data_store/health_data_store.dart'; -import 'package:jsaver/jSaver.dart'; import 'package:path/path.dart'; +import 'package:persistent_user_dir_access_android/persistent_user_dir_access_android.dart'; import 'package:provider/provider.dart'; import 'package:sqflite/sqflite.dart'; @@ -74,11 +74,13 @@ class ExportButtonBar extends StatelessWidget { _showError(messenger, localizations.errCantReadFile); return; } + if (!context.mounted) return; final converter = CsvConverter( Provider.of(context, listen: false), Provider.of(context, listen: false), await RepositoryProvider.of(context).getAll(), ); + if (!context.mounted) return; final importedRecords = await showImportPreview( context, CsvRecordParsingActor( @@ -183,8 +185,9 @@ void performExport(BuildContext context, [AppLocalizations? localizations]) asyn switch (exportSettings.exportFormat) { case ExportFormat.db: final path = join(await getDatabasesPath(), 'bp.db'); + final data = await File(path).readAsBytes(); - if (context.mounted) await _exportFile(context, path, '$filename.db', 'application/vnd.sqlite3'); + if (context.mounted) await _exportData(context, data, '$filename.db', 'application/vnd.sqlite3'); break; case ExportFormat.csv: final csvConverter = CsvConverter( @@ -224,29 +227,13 @@ Future> _getEntries(BuildContext context) async { return entries; } -/// Save to default export path or share by providing a path. -Future _exportFile(BuildContext context, String path, String fullFileName, String mimeType) async { - final settings = Provider.of(context, listen: false); - if (settings.defaultExportDir.isEmpty) { - await PlatformClient.shareFile(path, mimeType, fullFileName); - } else { - await JSaver.instance.save( - fromPath: path, - androidPathOptions: AndroidPathOptions(toDefaultDirectory: true), - ); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.success(settings.defaultExportDir)),),); - } -} - /// Save to default export path or share by providing binary data. Future _exportData(BuildContext context, Uint8List data, String fullFileName, String mimeType) async { final settings = Provider.of(context, listen: false); - if (settings.defaultExportDir.isEmpty) { + if (settings.defaultExportDir.isEmpty || !Platform.isAndroid) { await PlatformClient.shareData(data, mimeType, fullFileName); } else { - final file = File(joinPath(Directory.systemTemp.path, fullFileName)); - file.writeAsBytesSync(data); - await _exportFile(context, file.path, fullFileName, mimeType); + const userDir = PersistentUserDirAccessAndroid(); + await userDir.writeFile(settings.defaultExportDir, fullFileName, mimeType, data); } } diff --git a/app/lib/features/settings/export_import_screen.dart b/app/lib/features/settings/export_import_screen.dart index 0f20ef5d..f7e7e1eb 100644 --- a/app/lib/features/settings/export_import_screen.dart +++ b/app/lib/features/settings/export_import_screen.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:blood_pressure_app/components/disabled.dart'; import 'package:blood_pressure_app/data_util/interval_picker.dart'; import 'package:blood_pressure_app/features/export_import/active_field_customization.dart'; @@ -10,7 +12,7 @@ import 'package:blood_pressure_app/model/storage/export_columns_store.dart'; import 'package:blood_pressure_app/model/storage/storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:jsaver/jSaver.dart'; +import 'package:persistent_user_dir_access_android/persistent_user_dir_access_android.dart'; import 'package:provider/provider.dart'; /// Screen to configure and perform exports and imports of blood pressure values. @@ -45,19 +47,20 @@ class ExportImportScreen extends StatelessWidget { disabled: settings.exportFormat == ExportFormat.db, child: const IntervalPicker(type: IntervalStoreManagerLocation.exportPage,), ), - ListTile( - title: Text(localizations.exportDir), - subtitle: settings.defaultExportDir.isNotEmpty ? Text(settings.defaultExportDir) : null, - trailing: settings.defaultExportDir.isEmpty ? const Icon(Icons.folder_open) : const Icon(Icons.delete), - onTap: () async { - if (settings.defaultExportDir.isEmpty) { - final appDir = await JSaver.instance.setDefaultSavingDirectory(); - settings.defaultExportDir = appDir.value; - } else { - settings.defaultExportDir = ''; - } - }, - ), + if (Platform.isAndroid) // only supported on android + ListTile( + title: Text(localizations.exportDir), + subtitle: settings.defaultExportDir.isNotEmpty ? Text(settings.defaultExportDir) : null, + trailing: settings.defaultExportDir.isEmpty ? const Icon(Icons.folder_open) : const Icon(Icons.delete), + onTap: () async { + if (settings.defaultExportDir.isEmpty) { + final uri = await const PersistentUserDirAccessAndroid().requestDirectoryUri(); + settings.defaultExportDir = uri ?? ''; + } else { + settings.defaultExportDir = ''; + } + }, + ), SwitchListTile( title: Text(localizations.exportAfterEveryInput), subtitle: Text(localizations.exportAfterEveryInputDesc), diff --git a/app/pubspec.lock b/app/pubspec.lock index dd10c8ef..1541e368 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -480,14 +480,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.1" - jsaver: - dependency: "direct main" - description: - name: jsaver - sha256: "84136add8bafdde71b30d286bd3ab2629aad0067a94aaf04665956f8e765d205" - url: "https://pub.dev" - source: hosted - version: "1.3.0" json_annotation: dependency: transitive description: @@ -500,18 +492,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -688,6 +680,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.11.1" + persistent_user_dir_access_android: + dependency: "direct main" + description: + name: persistent_user_dir_access_android + sha256: "9256440259dc9b4454615f9346237f1047db43ef254841b70aaa616837ab0d39" + url: "https://pub.dev" + source: hosted + version: "0.0.1" petitparser: dependency: transitive description: @@ -852,7 +852,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -1097,10 +1097,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 6fef0ed0..853f557a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -31,16 +31,14 @@ dependencies: path: ../health_data_store/ flutter_bloc: ^8.1.6 flutter_blue_plus: ^1.32.12 - - # can become one custom dependency + archive: ^3.6.1 file_picker: ^8.1.2 - jsaver: ^1.3.0 fluttertoast: ^8.2.8 app_settings: ^5.1.1 # desktop only sqflite_common_ffi: ^2.3.3 - archive: ^3.6.1 + persistent_user_dir_access_android: ^0.0.1 dev_dependencies: integration_test: From ce55d988ee927a9a917428746825e50aa8a1d5f6 Mon Sep 17 00:00:00 2001 From: derdilla <82763757+NobodyForNothing@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:34:24 +0200 Subject: [PATCH 3/3] Fix PR builds (#443) * Set up correct java version * Fix PR bot not clearing label on failure --- .github/workflows/app-CI.yml | 10 ++++++++-- .github/workflows/pr.yml | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/app-CI.yml b/.github/workflows/app-CI.yml index 02914465..378ff132 100644 --- a/.github/workflows/app-CI.yml +++ b/.github/workflows/app-CI.yml @@ -52,7 +52,7 @@ jobs: channel: ${{ env.FLUTTER_CHANNEL }} cache: true - name: Disable analytics - run: flutter config --no-analytics + run: flutter config --no-analytics --suppress-analytics - name: Generate mock code run: | flutter pub get @@ -179,7 +179,13 @@ jobs: channel: ${{ env.FLUTTER_CHANNEL }} cache: true - name: Disable analytics - run: flutter config --no-analytics + run: flutter config --no-analytics --suppress-analytics + - name: Setup java + uses: actions/setup-java@v2 + with: + java-version: "17" + distribution: "temurin" + cache: 'gradle' - name: Build apk run: flutter build apk --flavor github --debug working-directory: app diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ea38c972..f3d9afc7 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -44,7 +44,13 @@ jobs: channel: ${{ env.FLUTTER_CHANNEL }} cache: true - name: Disable analytics - run: flutter config --no-analytics + run: flutter config --no-analytics --suppress-analytics + - name: Setup java + uses: actions/setup-java@v2 + with: + java-version: "17" + distribution: "temurin" + cache: 'gradle' - name: Build apk run: flutter build apk --flavor github --debug working-directory: app @@ -53,6 +59,7 @@ jobs: name: build-results path: app/build/app/outputs/flutter-apk - uses: mondeja/remove-labels-gh-action@v2 + if: always() with: token: ${{ secrets.GITHUB_TOKEN }} labels: build-apk