Commit 256c7cf

derdilla <contact@derdilla.com>
2024-09-20 06:18:07
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
1 parent 9588520
.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
.github/workflows/coverage-overview.yml
@@ -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
.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
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
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
 }
 
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<CsvExportSettings>(context, listen: false),
                       Provider.of<ExportColumnsManager>(context, listen: false),
                       await RepositoryProvider.of<MedicineRepository>(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<List<FullEntry>> _getEntries(BuildContext context) async {
   return entries;
 }
 
-/// Save to default export path or share by providing a path.
-Future<void> _exportFile(BuildContext context, String path, String fullFileName, String mimeType) async {
-  final settings = Provider.of<ExportSettings>(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<void> _exportData(BuildContext context, Uint8List data, String fullFileName, String mimeType) async {
   final settings = Provider.of<ExportSettings>(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);
   }
 }
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),
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:
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: