From 95ea711e9720de373d48844c01e9d36723e0fbcb Mon Sep 17 00:00:00 2001 From: Miroslav Mazel Date: Sat, 25 Nov 2023 00:57:27 +0100 Subject: [PATCH 1/4] Weight entries - buggy test --- lib/widgets/measurements/entries.dart | 104 ++++++++++---------------- lib/widgets/weight/entries_list.dart | 104 ++++++++++---------------- test/weight/weight_screen_test.dart | 12 ++- 3 files changed, 87 insertions(+), 133 deletions(-) diff --git a/lib/widgets/measurements/entries.dart b/lib/widgets/measurements/entries.dart index c3cf8337..cb4134ed 100644 --- a/lib/widgets/measurements/entries.dart +++ b/lib/widgets/measurements/entries.dart @@ -52,72 +52,46 @@ class EntriesList extends StatelessWidget { final currentEntry = _category.entries[index]; final provider = Provider.of(context, listen: false); - return Dismissible( - key: Key(currentEntry.id.toString()), - onDismissed: (direction) { - if (direction == DismissDirection.endToStart) { - // Delete entry from DB - provider.deleteEntry(currentEntry.id!, currentEntry.category); + return Card( + child: ListTile( + title: Text('${currentEntry.value} ${_category.unit}'), + subtitle: Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode) + .format(currentEntry.date), + ), + trailing: PopupMenuButton( + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + child: Text(AppLocalizations.of(context).edit), + onTap: () => Navigator.pushNamed( + context, + FormScreen.routeName, + arguments: FormScreenArguments( + AppLocalizations.of(context).edit, + MeasurementEntryForm(currentEntry.category, currentEntry), + ), + )), + PopupMenuItem( + child: Text(AppLocalizations.of(context).delete), + onTap: () async { + // Delete entry from DB + await provider.deleteEntry(currentEntry.id!, currentEntry.category); - // and inform the user - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context).successfullyDeleted, - textAlign: TextAlign.center, - ), - ), - ); - } - }, - confirmDismiss: (direction) async { - // Edit entry - if (direction == DismissDirection.startToEnd) { - Navigator.pushNamed( - context, - FormScreen.routeName, - arguments: FormScreenArguments( - AppLocalizations.of(context).edit, - MeasurementEntryForm(currentEntry.category, currentEntry), - ), - ); - return false; - } - return true; - }, - secondaryBackground: Container( - color: Theme.of(context).colorScheme.error, - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 4, - ), - child: const Icon( - Icons.delete, - color: Colors.white, - ), - ), - background: Container( - color: wgerPrimaryButtonColor, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 20), - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 4, - ), - child: const Icon( - Icons.edit, - color: Colors.white, - ), - ), - child: Card( - child: ListTile( - title: Text('${currentEntry.value} ${_category.unit}'), - subtitle: Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode) - .format(currentEntry.date), - ), + // and inform the user + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context).successfullyDeleted, + textAlign: TextAlign.center, + ), + ), + ); + } + }) + ]; + }, ), ), ); diff --git a/lib/widgets/weight/entries_list.dart b/lib/widgets/weight/entries_list.dart index 8377ec5b..ff3f033e 100644 --- a/lib/widgets/weight/entries_list.dart +++ b/lib/widgets/weight/entries_list.dart @@ -60,72 +60,46 @@ class WeightEntriesList extends StatelessWidget { itemCount: weightProvider.items.length, itemBuilder: (context, index) { final currentEntry = weightProvider.items[index]; - return Dismissible( - key: Key(currentEntry.id.toString()), - onDismissed: (direction) { - if (direction == DismissDirection.endToStart) { - // Delete entry from DB - weightProvider.deleteEntry(currentEntry.id!); + return Card( + child: ListTile( + title: Text('${currentEntry.weight} kg'), + subtitle: Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode) + .format(currentEntry.date), + ), + trailing: PopupMenuButton( + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + child: Text(AppLocalizations.of(context).edit), + onTap: () => Navigator.pushNamed( + context, + FormScreen.routeName, + arguments: FormScreenArguments( + AppLocalizations.of(context).edit, + WeightForm(currentEntry), + ), + )), + PopupMenuItem( + child: Text(AppLocalizations.of(context).delete), + onTap: () async { + // Delete entry from DB + await weightProvider.deleteEntry(currentEntry.id!); - // and inform the user - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context).successfullyDeleted, - textAlign: TextAlign.center, - ), - ), - ); - } - }, - confirmDismiss: (direction) async { - // Edit entry - if (direction == DismissDirection.startToEnd) { - Navigator.pushNamed( - context, - FormScreen.routeName, - arguments: FormScreenArguments( - AppLocalizations.of(context).edit, - WeightForm(currentEntry), - ), - ); - return false; - } - return true; - }, - secondaryBackground: Container( - color: Theme.of(context).colorScheme.error, - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 4, - ), - child: const Icon( - Icons.delete, - color: Colors.white, - ), - ), - background: Container( - // color: wgerPrimaryButtonColor, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 20), - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 4, - ), - child: const Icon( - Icons.edit, - color: Colors.white, - ), - ), - child: Card( - child: ListTile( - title: Text('${currentEntry.weight} kg'), - subtitle: Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode) - .format(currentEntry.date), - ), + // and inform the user + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context).successfullyDeleted, + textAlign: TextAlign.center, + ), + ), + ); + } + }) + ]; + }, ), ), ); diff --git a/test/weight/weight_screen_test.dart b/test/weight/weight_screen_test.dart index 10bd75bd..f566e8e1 100644 --- a/test/weight/weight_screen_test.dart +++ b/test/weight/weight_screen_test.dart @@ -58,15 +58,21 @@ void main() { expect(find.text('Weight'), findsOneWidget); expect(find.byType(MeasurementChartWidgetFl), findsOneWidget); - expect(find.byType(Dismissible), findsNWidgets(2)); + expect(find.byType(Card), findsNWidgets(2)); expect(find.byType(ListTile), findsNWidgets(2)); }); - testWidgets('Test deleting an item by dragging the dismissible', (WidgetTester tester) async { + testWidgets('Test deleting an item using the Delete button', (WidgetTester tester) async { await tester.pumpWidget(createWeightScreen()); - await tester.drag(find.byKey(const Key('1')), const Offset(-500.0, 0.0)); + expect(find.byType(ListTile), findsNWidgets(2)); + + await tester.tap(find.byTooltip('Show menu').first); await tester.pumpAndSettle(); + + await tester.tap(find.text('Delete')); + await tester.pumpAndSettle(); + verify(mockWeightProvider.deleteEntry(1)).called(1); expect(find.byType(ListTile), findsOneWidget); }); From 79522575f6cf545f02d575156603dc798f640040 Mon Sep 17 00:00:00 2001 From: Miroslav Mazel Date: Wed, 10 Jan 2024 18:41:59 +0100 Subject: [PATCH 2/4] Updated Flatpak scripts --- .github/workflows/build-release.yml | 2 +- ...pdata.xml => de.wger.flutter.metainfo.xml} | 0 flatpak/flatpak_meta.json | 8 +- flatpak/scripts/flatpak_packager.dart | 123 +++++++------ flatpak/scripts/flatpak_shared.dart | 166 +++++++++++------- flatpak/scripts/manifest_generator.dart | 43 +++-- 6 files changed, 208 insertions(+), 134 deletions(-) rename flatpak/{de.wger.flutter.appdata.xml => de.wger.flutter.metainfo.xml} (100%) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index b8e61ce9..5aa9fc47 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -63,7 +63,7 @@ jobs: flutter build linux --release cd flatpak/scripts dart pub get - dart flatpak_packager.dart --meta ../flatpak_meta.json --github + dart flatpak_packager.dart --meta ../flatpak_meta.json --github --addTodaysVersion ${{ env.VERSION }} - name: Build AAB run: flutter build appbundle --release diff --git a/flatpak/de.wger.flutter.appdata.xml b/flatpak/de.wger.flutter.metainfo.xml similarity index 100% rename from flatpak/de.wger.flutter.appdata.xml rename to flatpak/de.wger.flutter.metainfo.xml diff --git a/flatpak/flatpak_meta.json b/flatpak/flatpak_meta.json index 5d408ead..253cf82e 100644 --- a/flatpak/flatpak_meta.json +++ b/flatpak/flatpak_meta.json @@ -3,14 +3,8 @@ "lowercaseAppName": "wger", "githubReleaseOrganization": "wger-project", "githubReleaseProject": "flutter", - "localReleases": [ - - ], - "localReleaseAssets": [ - - ], "localLinuxBuildDir": "../build/linux", - "appDataPath": "de.wger.flutter.appdata.xml", + "appStreamPath": "de.wger.flutter.metainfo.xml", "desktopPath": "de.wger.flutter.desktop", "icons": { "64x64": "logo64.png", diff --git a/flatpak/scripts/flatpak_packager.dart b/flatpak/scripts/flatpak_packager.dart index 5346a605..92cf2768 100644 --- a/flatpak/scripts/flatpak_packager.dart +++ b/flatpak/scripts/flatpak_packager.dart @@ -1,17 +1,27 @@ // ignore_for_file: avoid_print import 'dart:io'; - import 'flatpak_shared.dart'; +/// Creates an archive containing all the sources for the Flatpak package for a +/// specific architecture. +/// /// arguments: /// --meta [file] +/// Required argument for providing the metadata file for this script. + /// --github +/// Use this option to pull release info from Github rather than the metadata file. + +/// --addTodaysVersion [version] +/// If pulling data from Github, this provides a way to specify the release to be released today. + void main(List arguments) async { - if (Platform.isWindows) { - throw Exception('Must be run under a UNIX-like operating system.'); + if (!Platform.isLinux) { + throw Exception('Must be run under Linux'); } + // PARSE ARGUMENTS final metaIndex = arguments.indexOf('--meta'); if (metaIndex == -1) { throw Exception( @@ -22,123 +32,136 @@ void main(List arguments) async { } final metaFile = File(arguments[metaIndex + 1]); - if (!metaFile.existsSync()) { + if (!(await metaFile.exists())) { throw Exception('The provided metadata file does not exist.'); } - final meta = FlatpakMeta.fromJson(metaFile); - final fetchFromGithub = arguments.contains('--github'); + final addTodaysVersionIndex = arguments.indexOf('--addTodaysVersion'); + if (addTodaysVersionIndex != -1 && arguments.length == addTodaysVersionIndex + 1) { + throw Exception('The --addTodaysVersion flag must be followed by the version name.'); + } + + final addedTodaysVersion = + addTodaysVersionIndex != -1 ? arguments[addTodaysVersionIndex + 1] : null; + + // GENERATE PACKAGE + + final meta = FlatpakMeta.fromJson(metaFile, skipLocalReleases: fetchFromGithub); + final outputDir = Directory('${Directory.current.path}/flatpak_generator_exports'); - outputDir.createSync(); + await outputDir.create(); - final packageGenerator = PackageGenerator(inputDir: metaFile.parent, meta: meta); + final packageGenerator = PackageGenerator( + inputDir: metaFile.parent, meta: meta, addedTodaysVersion: addedTodaysVersion); - packageGenerator.generatePackage( - outputDir, - PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64, - fetchFromGithub, - ); + await packageGenerator.generatePackage( + outputDir, + await PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64, + fetchFromGithub); } class PackageGenerator { final Directory inputDir; final FlatpakMeta meta; - final Map shaByArch = {}; + final String? addedTodaysVersion; - PackageGenerator({required this.inputDir, required this.meta}); + PackageGenerator({required this.inputDir, required this.meta, required this.addedTodaysVersion}); Future generatePackage( Directory outputDir, CPUArchitecture arch, bool fetchReleasesFromGithub) async { - final tempDir = outputDir.createTempSync('flutter_generator_temp'); + final tempDir = await outputDir.createTemp('flutter_generator_temp'); final appId = meta.appId; // desktop file final desktopFile = File('${inputDir.path}/${meta.desktopPath}'); - if (!desktopFile.existsSync()) { + if (!(await desktopFile.exists())) { throw Exception( 'The desktop file does not exist under the specified path: ${desktopFile.path}'); } - desktopFile.copySync('${tempDir.path}/$appId.desktop'); + await desktopFile.copy('${tempDir.path}/$appId.desktop'); // icons final iconTempDir = Directory('${tempDir.path}/icons'); for (final icon in meta.icons) { final iconFile = File('${inputDir.path}/${icon.path}'); - if (!iconFile.existsSync()) { + if (!(await iconFile.exists())) { throw Exception('The icon file ${iconFile.path} does not exist.'); } final iconSubdir = Directory('${iconTempDir.path}/${icon.type}'); - iconSubdir.createSync(recursive: true); - iconFile.copySync('${iconSubdir.path}/${icon.getFilename(appId)}'); + await iconSubdir.create(recursive: true); + await iconFile.copy('${iconSubdir.path}/${icon.getFilename(appId)}'); } - // appdata file - final origAppDataFile = File('${inputDir.path}/${meta.appDataPath}'); - if (!origAppDataFile.existsSync()) { + // AppStream metainfo file + final origAppStreamFile = File('${inputDir.path}/${meta.appStreamPath}'); + if (!(await origAppStreamFile.exists())) { throw Exception( - 'The app data file does not exist under the specified path: ${origAppDataFile.path}'); + 'The app data file does not exist under the specified path: ${origAppStreamFile.path}'); } - final editedAppDataContent = AppDataModifier.replaceVersions( - origAppDataFile.readAsStringSync(), await meta.getReleases(fetchReleasesFromGithub)); + final editedAppStreamContent = AppStreamModifier.replaceVersions( + await origAppStreamFile.readAsString(), + await meta.getReleases(fetchReleasesFromGithub, addedTodaysVersion)); - final editedAppDataFile = File('${tempDir.path}/$appId.appdata.xml'); - editedAppDataFile.writeAsStringSync(editedAppDataContent); + final editedAppStreamFile = File('${tempDir.path}/$appId.metainfo.xml'); + await editedAppStreamFile.writeAsString(editedAppStreamContent); // build files final bundlePath = '${inputDir.path}/${meta.localLinuxBuildDir}/${arch.flutterDirName}/release/bundle'; final buildDir = Directory(bundlePath); - if (!buildDir.existsSync()) { + if (!(await buildDir.exists())) { throw Exception( 'The linux build directory does not exist under the specified path: ${buildDir.path}'); } final destDir = Directory('${tempDir.path}/bin'); - destDir.createSync(); + await destDir.create(); - final baseFileName = '${meta.lowercaseAppName}-linux-${arch.flatpakArchCode}'; + final baseFilename = '${meta.lowercaseAppName}-linux-${arch.flatpakArchCode}'; + final packagePath = '${outputDir.absolute.path}/$baseFilename.tar.gz'; + final shaPath = '${outputDir.absolute.path}/$baseFilename.sha256'; + + await Process.run('cp', ['-r', '${buildDir.absolute.path}/.', destDir.absolute.path]); + await Process.run('tar', ['-czvf', packagePath, '.'], workingDirectory: tempDir.absolute.path); - final packagePath = '${outputDir.absolute.path}/$baseFileName.tar.gz'; - Process.runSync('cp', ['-r', '${buildDir.absolute.path}/.', destDir.absolute.path]); - Process.runSync('tar', ['-czvf', packagePath, '.'], workingDirectory: tempDir.absolute.path); print('Generated $packagePath'); - final preShasum = Process.runSync('shasum', ['-a', '256', packagePath]); - final sha256 = preShasum.stdout.toString().split(' ').first; + final preShasum = await Process.run('shasum', ['-a', '256', packagePath]); + final shasum = preShasum.stdout.toString().split(' ').first; - final shaFile = await File('${outputDir.path}/$baseFileName.sha256').writeAsString(sha256); - print('Generated ${shaFile.path}'); + await File(shaPath).writeAsString(shasum); - shaByArch.putIfAbsent(arch, () => sha256); + print('Generated $shaPath'); - tempDir.deleteSync(recursive: true); + await tempDir.delete(recursive: true); } - static bool runningOnARM() { - final unameRes = Process.runSync('uname', ['-m']); + static Future runningOnARM() async { + final unameRes = await Process.run('uname', ['-m']); final unameString = unameRes.stdout.toString().trimLeft(); return unameString.startsWith('arm') || unameString.startsWith('aarch'); } } -// updates releases in ${appName}.appdata.xml -class AppDataModifier { - static String replaceVersions(String origAppDataContent, List versions) { +// updates releases in ${appName}.metainfo.xml +class AppStreamModifier { + static String replaceVersions(String origAppStreamContent, List versions) { final joinedReleases = versions.map((v) => '\t\t').join('\n'); - final releasesSection = '\n$joinedReleases\n\t'; //todo check this - if (origAppDataContent.contains('') .replaceFirst(RegExp(''), releasesSection) .replaceAll('<~>', '\n'); } else { - return origAppDataContent.replaceFirst('', '\n\t$releasesSection\n'); + return origAppStreamContent.replaceFirst( + '', '\n\t$releasesSection\n'); } } } diff --git a/flatpak/scripts/flatpak_shared.dart b/flatpak/scripts/flatpak_shared.dart index f411aabe..d3ad12ad 100644 --- a/flatpak/scripts/flatpak_shared.dart +++ b/flatpak/scripts/flatpak_shared.dart @@ -3,20 +3,21 @@ import 'dart:io'; import 'package:http/http.dart' as http; +/// Shared files for the two Flatpak-related scripts. + class Release { final String version; - final String date; //todo add resources + final String date; //TODO add resources Release({required this.version, required this.date}); } enum CPUArchitecture { x86_64('x86_64', 'x64'), - aarch64('aarch64', 'aarch64'); + aarch64('aarch64', 'arm64'); final String flatpakArchCode; final String flutterDirName; - const CPUArchitecture(this.flatpakArchCode, this.flutterDirName); } @@ -43,8 +44,9 @@ class Icon { _fileExtension = path.split('.').last; } - String getFilename(String appId) => - (type == _symbolicType) ? '$appId-symbolic.$_fileExtension' : '$appId.$_fileExtension'; + String getFilename(String appId) => (type == _symbolicType) + ? '$appId-symbolic.$_fileExtension' + : '$appId.$_fileExtension'; } class GithubReleases { @@ -55,25 +57,26 @@ class GithubReleases { GithubReleases(this.githubReleaseOrganization, this.githubReleaseProject); - Future> getReleases() async { + Future> getReleases(bool canBeEmpty) async { if (_releases == null) { - await _fetchReleasesAndAssets(); + await _fetchReleasesAndAssets(canBeEmpty); } return _releases!; } Future?> getLatestReleaseAssets() async { if (_releases == null) { - await _fetchReleasesAndAssets(); + await _fetchReleasesAndAssets(false); } return _latestReleaseAssets; } - Future _fetchReleasesAndAssets() async { + Future _fetchReleasesAndAssets(bool canBeEmpty) async { final releaseJsonContent = (await http.get(Uri( scheme: 'https', host: 'api.github.com', - path: '/repos/$githubReleaseOrganization/$githubReleaseProject/releases'))) + path: + '/repos/$githubReleaseOrganization/$githubReleaseProject/releases'))) .body; final decodedJson = jsonDecode(releaseJsonContent) as List; @@ -84,27 +87,33 @@ class GithubReleases { await Future.forEach(decodedJson, (dynamic releaseDynamic) async { final releaseMap = releaseDynamic as Map; - final releaseDateAndTime = DateTime.parse(releaseMap['published_at'] as String); - final releaseDateString = releaseDateAndTime.toIso8601String().split('T').first; + final releaseDateAndTime = + DateTime.parse(releaseMap['published_at'] as String); + final releaseDateString = + releaseDateAndTime.toIso8601String().split('T').first; if (latestReleaseAssetDate == null || (latestReleaseAssetDate?.compareTo(releaseDateAndTime) == -1)) { - final assets = await _parseReleaseAssets(releaseMap['assets'] as List); + final assets = + await _parseGithubReleaseAssets(releaseMap['assets'] as List); if (assets != null) { _latestReleaseAssets = assets; latestReleaseAssetDate = releaseDateAndTime; } } - releases.add(Release(version: releaseMap['name'] as String, date: releaseDateString)); + releases.add(Release( + version: releaseMap['name'] as String, date: releaseDateString)); }); - if (releases.isNotEmpty) { + if (releases.isNotEmpty || canBeEmpty) { _releases = releases; + } else { + throw Exception("Github must contain at least 1 release."); } } - Future?> _parseReleaseAssets(List assetMaps) async { + Future?> _parseGithubReleaseAssets(List assetMaps) async { String? x64TarballUrl; String? x64Sha; String? aarch64TarballUrl; @@ -115,7 +124,8 @@ class GithubReleases { final downloadUrl = amMap['browser_download_url'] as String; final filename = amMap['name'] as String; final fileExtension = filename.substring(filename.indexOf('.') + 1); - final filenameWithoutExtension = filename.substring(0, filename.indexOf('.')); + final filenameWithoutExtension = + filename.substring(0, filename.indexOf('.')); final arch = filenameWithoutExtension.endsWith('aarch64') ? CPUArchitecture.aarch64 @@ -166,15 +176,14 @@ class GithubReleases { return res.isEmpty ? null : res; } - Future _readSha(String shaUrl) async { - return (await http.get(Uri.parse(shaUrl))).body.split(' ').first; - } + Future _readSha(String shaUrl) async => + (await http.get(Uri.parse(shaUrl))).body.split(' ').first; } class FlatpakMeta { final String appId; final String lowercaseAppName; - final String appDataPath; + final String appStreamPath; final String desktopPath; final List icons; @@ -202,7 +211,7 @@ class FlatpakMeta { required List? localReleases, required List? localReleaseAssets, required this.localLinuxBuildDir, - required this.appDataPath, + required this.appStreamPath, required this.desktopPath, required this.icons, required this.freedesktopRuntime, @@ -212,88 +221,117 @@ class FlatpakMeta { : _localReleases = localReleases, _localReleaseAssets = localReleaseAssets { if (githubReleaseOrganization != null && githubReleaseProject != null) { - _githubReleases = GithubReleases(githubReleaseOrganization!, githubReleaseProject!); + _githubReleases = + GithubReleases(githubReleaseOrganization!, githubReleaseProject!); } } - Future> getReleases(bool fetchReleasesFromGithub) async { + Future> getReleases( + bool fetchReleasesFromGithub, String? addedTodaysVersion) async { + final releases = List.empty(growable: true); + if (addedTodaysVersion != null) { + releases.add(Release( + version: addedTodaysVersion, + date: DateTime.now().toIso8601String().split("T").first)); + } if (fetchReleasesFromGithub) { if (_githubReleases == null) { throw Exception( 'Metadata must include Github repository info if fetching releases from Github.'); } - return await _githubReleases!.getReleases(); + releases.addAll( + await _githubReleases!.getReleases(addedTodaysVersion != null)); } else { - if (_localReleases == null) { - throw Exception('Metadata must include releases if not fetching releases from Github.'); + if (_localReleases == null && addedTodaysVersion == null) { + throw Exception( + 'Metadata must include releases if not fetching releases from Github.'); + } + if (_localReleases?.isNotEmpty ?? false) { + releases.addAll(_localReleases!); } - return _localReleases!; } + return releases; } - Future?> getReleaseAssets(bool fetchReleasesFromGithub) async { + Future?> getLatestReleaseAssets( + bool fetchReleasesFromGithub) async { if (fetchReleasesFromGithub) { if (_githubReleases == null) { throw Exception( 'Metadata must include Github repository info if fetching releases from Github.'); } - return _githubReleases!.getLatestReleaseAssets(); + return await _githubReleases!.getLatestReleaseAssets(); } else { if (_localReleases == null) { - throw Exception('Metadata must include releases if not fetching releases from Github.'); + throw Exception( + 'Metadata must include releases if not fetching releases from Github.'); } return _localReleaseAssets; } } - static FlatpakMeta fromJson(File jsonFile) { + static FlatpakMeta fromJson(File jsonFile, {bool skipLocalReleases = false}) { try { final dynamic json = jsonDecode(jsonFile.readAsStringSync()); return FlatpakMeta( appId: json['appId'] as String, lowercaseAppName: json['lowercaseAppName'] as String, - githubReleaseOrganization: json['githubReleaseOrganization'] as String?, + githubReleaseOrganization: + json['githubReleaseOrganization'] as String?, githubReleaseProject: json['githubReleaseProject'] as String?, - localReleases: (json['localReleases'] as List?)?.map((dynamic r) { - final rMap = r as Map; - return Release(version: rMap['version'] as String, date: rMap['date'] as String); - }).toList(), - localReleaseAssets: (json['localReleaseAssets'] as List?)?.map((dynamic ra) { - final raMap = ra as Map; - final archString = raMap['arch'] as String; - final arch = (archString == CPUArchitecture.x86_64.flatpakArchCode) - ? CPUArchitecture.x86_64 - : (archString == CPUArchitecture.aarch64.flatpakArchCode) - ? CPUArchitecture.aarch64 - : null; - if (arch == null) { - throw Exception( - 'Architecture must be either "${CPUArchitecture.x86_64.flatpakArchCode}" or "${CPUArchitecture.aarch64.flatpakArchCode}"'); - } - final tarballPath = '${jsonFile.parent.path}/${raMap['tarballPath'] as String}'; - final preShasum = Process.runSync('shasum', ['-a', '256', tarballPath]); - final shasum = preShasum.stdout.toString().split(' ').first; - if (preShasum.exitCode != 0) { - throw Exception(preShasum.stderr); - } - return ReleaseAsset( - arch: arch, - tarballUrlOrPath: tarballPath, - isRelativeLocalPath: true, - tarballSha256: shasum); - }).toList(), + localReleases: skipLocalReleases + ? null + : (json['localReleases'] as List?)?.map((dynamic r) { + final rMap = r as Map; + return Release( + version: rMap['version'] as String, + date: rMap['date'] as String); + }).toList(), + localReleaseAssets: skipLocalReleases + ? null + : (json['localReleaseAssets'] as List?)?.map((dynamic ra) { + final raMap = ra as Map; + final archString = raMap['arch'] as String; + final arch = (archString == + CPUArchitecture.x86_64.flatpakArchCode) + ? CPUArchitecture.x86_64 + : (archString == CPUArchitecture.aarch64.flatpakArchCode) + ? CPUArchitecture.aarch64 + : null; + if (arch == null) { + throw Exception( + 'Architecture must be either "${CPUArchitecture.x86_64.flatpakArchCode}" or "${CPUArchitecture.aarch64.flatpakArchCode}"'); + } + final tarballFile = File( + '${jsonFile.parent.path}/${raMap['tarballPath'] as String}'); + final tarballPath = tarballFile.absolute.path; + final preShasum = + Process.runSync('shasum', ['-a', '256', tarballPath]); + final shasum = preShasum.stdout.toString().split(' ').first; + if (preShasum.exitCode != 0) { + throw Exception(preShasum.stderr); + } + return ReleaseAsset( + arch: arch, + tarballUrlOrPath: tarballPath, + isRelativeLocalPath: true, + tarballSha256: shasum); + }).toList(), localLinuxBuildDir: json['localLinuxBuildDir'] as String, - appDataPath: json['appDataPath'] as String, + appStreamPath: json['appStreamPath'] as String, desktopPath: json['desktopPath'] as String, icons: (json['icons'] as Map).entries.map((mapEntry) { - return Icon(type: mapEntry.key as String, path: mapEntry.value as String); + return Icon( + type: mapEntry.key as String, path: mapEntry.value as String); }).toList(), freedesktopRuntime: json['freedesktopRuntime'] as String, buildCommandsAfterUnpack: (json['buildCommandsAfterUnpack'] as List?) ?.map((dynamic bc) => bc as String) .toList(), extraModules: json['extraModules'] as List?, - finishArgs: (json['finishArgs'] as List).map((dynamic fa) => fa as String).toList()); + finishArgs: (json['finishArgs'] as List) + .map((dynamic fa) => fa as String) + .toList()); } catch (e) { throw Exception('Could not parse JSON file, due to this error:\n$e'); } diff --git a/flatpak/scripts/manifest_generator.dart b/flatpak/scripts/manifest_generator.dart index da27f860..8957a3d1 100644 --- a/flatpak/scripts/manifest_generator.dart +++ b/flatpak/scripts/manifest_generator.dart @@ -1,8 +1,20 @@ +// ignore_for_file: avoid_print + import 'dart:convert'; import 'dart:io'; - import 'flatpak_shared.dart'; +/// Generates the Flatpak manifest. +/// (Separate from the package generation, as those are generated per each +/// architecture.) +/// +/// arguments: +/// --meta [file] +/// Required argument for providing the metadata file for this script. + +/// --github +/// Use this option to pull release info from Github rather than the metadata file. + void main(List arguments) async { if (Platform.isWindows) { throw Exception('Must be run under a UNIX-like operating system.'); @@ -14,7 +26,8 @@ void main(List arguments) async { 'You must run this script with a metadata file argument, using the --meta flag.'); } if (arguments.length == metaIndex + 1) { - throw Exception('The --meta flag must be followed by the path to the metadata file.'); + throw Exception( + 'The --meta flag must be followed by the path to the metadata file.'); } final metaFile = File(arguments[metaIndex + 1]); @@ -22,21 +35,25 @@ void main(List arguments) async { throw Exception('The provided metadata file does not exist.'); } - final meta = FlatpakMeta.fromJson(metaFile); - final fetchFromGithub = arguments.contains('--github'); - final outputDir = Directory('${Directory.current.path}/flatpak_generator_exports'); + final meta = + FlatpakMeta.fromJson(metaFile, skipLocalReleases: fetchFromGithub); + + final outputDir = + Directory('${Directory.current.path}/flatpak_generator_exports'); outputDir.createSync(); final manifestGenerator = FlatpakManifestGenerator(meta); - final manifestContent = await manifestGenerator.generateFlatpakManifest(fetchFromGithub); + final manifestContent = + await manifestGenerator.generateFlatpakManifest(fetchFromGithub); final manifestPath = '${outputDir.path}/${meta.appId}.json'; final manifestFile = File(manifestPath); manifestFile.writeAsStringSync(manifestContent); print('Generated $manifestPath'); - final flathubJsonContent = await manifestGenerator.generateFlathubJson(fetchFromGithub); + final flathubJsonContent = + await manifestGenerator.generateFlathubJson(fetchFromGithub); if (flathubJsonContent != null) { final flathubJsonPath = '${outputDir.path}/flathub.json'; final flathubJsonFile = File(flathubJsonPath); @@ -56,7 +73,7 @@ class FlatpakManifestGenerator { Future generateFlatpakManifest(bool fetchFromGithub) async { final appName = meta.lowercaseAppName; final appId = meta.appId; - final assets = await meta.getReleaseAssets(fetchFromGithub); + final assets = await meta.getLatestReleaseAssets(fetchFromGithub); if (assets == null) { throw Exception('There are no associated assets.'); @@ -88,7 +105,7 @@ class FlatpakManifestGenerator { ...meta.icons.map((icon) => 'install -Dm644 $appName/icons/${icon.type}/${icon.getFilename(appId)} /app/share/icons/hicolor/${icon.type}/apps/${icon.getFilename(appId)}'), 'install -Dm644 $appName/$appId.desktop /app/share/applications/$appId.desktop', - 'install -Dm644 $appName/$appId.appdata.xml /app/share/applications/$appId.appdata.xml' + 'install -Dm644 $appName/$appId.metainfo.xml /app/share/metainfo/$appId.metainfo.xml' ], 'sources': assets .map((a) => { @@ -105,7 +122,7 @@ class FlatpakManifestGenerator { } Future generateFlathubJson(bool fetchFromGithub) async { - final assets = await meta.getReleaseAssets(fetchFromGithub); + final assets = await meta.getLatestReleaseAssets(fetchFromGithub); if (assets == null) { throw Exception('There are no associated assets.'); @@ -115,7 +132,8 @@ class FlatpakManifestGenerator { const encoder = JsonEncoder.withIndent(' '); - final onlyArchListInput = fetchFromGithub ? _githubArchSupport! : _localArchSupport!; + final onlyArchListInput = + fetchFromGithub ? _githubArchSupport! : _localArchSupport!; final onlyArchList = List.empty(growable: true); for (final e in onlyArchListInput.entries) { @@ -131,7 +149,8 @@ class FlatpakManifestGenerator { } } - void _lazyGenerateArchSupportMap(bool fetchFromGithub, List assets) { + void _lazyGenerateArchSupportMap( + bool fetchFromGithub, List assets) { if (fetchFromGithub) { if (_githubArchSupport == null) { _githubArchSupport = { From 0400d1b9c4e243c75621a74fb75da77a9ea0c4f5 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 6 Dec 2023 21:07:43 +0100 Subject: [PATCH 3/4] Small tweaks --- lib/l10n/app_en.arb | 3 +-- lib/widgets/nutrition/meal.dart | 2 +- lib/widgets/workouts/day.dart | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a44457c7..8618567a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -809,10 +809,9 @@ "@none__bodyweight_exercise_": { "description": "Generated entry for translation for server strings" }, - "editSchedule": "Edit schedule", "log": "Log", "@log": { - "description": "Log a specific meal" + "description": "Log a specific meal (imperative form)" }, "done": "Done" } diff --git a/lib/widgets/nutrition/meal.dart b/lib/widgets/nutrition/meal.dart index 2002c0ae..a9d71c13 100644 --- a/lib/widgets/nutrition/meal.dart +++ b/lib/widgets/nutrition/meal.dart @@ -94,7 +94,7 @@ class _MealWidgetState extends State { }, ), TextButton.icon( - label: Text(AppLocalizations.of(context).editSchedule), + label: Text(AppLocalizations.of(context).edit), onPressed: () { Navigator.pushNamed( context, diff --git a/lib/widgets/workouts/day.dart b/lib/widgets/workouts/day.dart index 6fc911f5..30df1359 100644 --- a/lib/widgets/workouts/day.dart +++ b/lib/widgets/workouts/day.dart @@ -194,7 +194,7 @@ class _WorkoutDayWidgetState extends State { ), TextButton.icon( icon: const Icon(Icons.calendar_month), - label: Text(AppLocalizations.of(context).editSchedule), + label: Text(AppLocalizations.of(context).edit), onPressed: () { Navigator.pushNamed( context, From 3f46277c7540e042880e95dae164bc1889834ca4 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 23 Jan 2024 14:58:53 +0100 Subject: [PATCH 4/4] Fix test Since we're mocking the provider no entries are actually being deleted anywhere --- lib/widgets/weight/entries_list.dart | 31 ++++++++++++++------------- test/weight/weight_provider_test.dart | 2 +- test/weight/weight_screen_test.dart | 13 +++++------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/widgets/weight/entries_list.dart b/lib/widgets/weight/entries_list.dart index ff3f033e..99f70310 100644 --- a/lib/widgets/weight/entries_list.dart +++ b/lib/widgets/weight/entries_list.dart @@ -81,23 +81,24 @@ class WeightEntriesList extends StatelessWidget { ), )), PopupMenuItem( - child: Text(AppLocalizations.of(context).delete), - onTap: () async { - // Delete entry from DB - await weightProvider.deleteEntry(currentEntry.id!); + child: Text(AppLocalizations.of(context).delete), + onTap: () async { + // Delete entry from DB + await weightProvider.deleteEntry(currentEntry.id!); - // and inform the user - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context).successfullyDeleted, - textAlign: TextAlign.center, - ), + // and inform the user + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context).successfullyDeleted, + textAlign: TextAlign.center, ), - ); - } - }) + ), + ); + } + }, + ) ]; }, ), diff --git a/test/weight/weight_provider_test.dart b/test/weight/weight_provider_test.dart index 6f4dccad..72488713 100644 --- a/test/weight/weight_provider_test.dart +++ b/test/weight/weight_provider_test.dart @@ -31,7 +31,7 @@ import 'weight_provider_test.mocks.dart'; @GenerateMocks([WgerBaseProvider]) void main() { - var mockBaseProvider = MockWgerBaseProvider(); + late MockWgerBaseProvider mockBaseProvider; setUp(() { mockBaseProvider = MockWgerBaseProvider(); diff --git a/test/weight/weight_screen_test.dart b/test/weight/weight_screen_test.dart index f566e8e1..49894e35 100644 --- a/test/weight/weight_screen_test.dart +++ b/test/weight/weight_screen_test.dart @@ -33,12 +33,14 @@ import 'weight_screen_test.mocks.dart'; @GenerateMocks([BodyWeightProvider]) void main() { - var mockWeightProvider = MockBodyWeightProvider(); + late MockBodyWeightProvider mockWeightProvider; - Widget createWeightScreen({locale = 'en'}) { + setUp(() { mockWeightProvider = MockBodyWeightProvider(); when(mockWeightProvider.items).thenReturn(getWeightEntries()); + }); + Widget createWeightScreen({locale = 'en'}) { return ChangeNotifierProvider( create: (context) => mockWeightProvider, child: MaterialApp( @@ -63,18 +65,18 @@ void main() { }); testWidgets('Test deleting an item using the Delete button', (WidgetTester tester) async { + // Arrange await tester.pumpWidget(createWeightScreen()); + // Act expect(find.byType(ListTile), findsNWidgets(2)); - await tester.tap(find.byTooltip('Show menu').first); await tester.pumpAndSettle(); + // Assert await tester.tap(find.text('Delete')); await tester.pumpAndSettle(); - verify(mockWeightProvider.deleteEntry(1)).called(1); - expect(find.byType(ListTile), findsOneWidget); }); testWidgets('Test the form on the body weight screen', (WidgetTester tester) async { @@ -89,7 +91,6 @@ void main() { testWidgets('Tests the localization of dates - EN', (WidgetTester tester) async { await tester.pumpWidget(createWeightScreen()); - // One in the entries list, one in the chart expect(find.text('1/1/2021'), findsOneWidget); expect(find.text('1/10/2021'), findsOneWidget); });