mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
7
.github/workflows/build-release.yml
vendored
7
.github/workflows/build-release.yml
vendored
@@ -12,10 +12,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'oracle'
|
||||
java-version: 17.x
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
@@ -64,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<String> 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<String> 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<CPUArchitecture, String> shaByArch = {};
|
||||
final String? addedTodaysVersion;
|
||||
|
||||
PackageGenerator({required this.inputDir, required this.meta});
|
||||
PackageGenerator({required this.inputDir, required this.meta, required this.addedTodaysVersion});
|
||||
|
||||
Future<void> 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<bool> 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<Release> versions) {
|
||||
// updates releases in ${appName}.metainfo.xml
|
||||
class AppStreamModifier {
|
||||
static String replaceVersions(String origAppStreamContent, List<Release> versions) {
|
||||
final joinedReleases =
|
||||
versions.map((v) => '\t\t<release version="${v.version}" date="${v.date}" />').join('\n');
|
||||
final releasesSection = '<releases>\n$joinedReleases\n\t</releases>'; //todo check this
|
||||
if (origAppDataContent.contains('<releases')) {
|
||||
return origAppDataContent
|
||||
final releasesSection = '<releases>\n$joinedReleases\n\t</releases>'; //TODO check this
|
||||
if (origAppStreamContent.contains('<releases')) {
|
||||
return origAppStreamContent
|
||||
.replaceAll('\n', '<~>')
|
||||
.replaceFirst(RegExp('<releases.*</releases>'), releasesSection)
|
||||
.replaceAll('<~>', '\n');
|
||||
} else {
|
||||
return origAppDataContent.replaceFirst('</component>', '\n\t$releasesSection\n</component>');
|
||||
return origAppStreamContent.replaceFirst(
|
||||
'</component>', '\n\t$releasesSection\n</component>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<Release>> getReleases() async {
|
||||
Future<List<Release>> getReleases(bool canBeEmpty) async {
|
||||
if (_releases == null) {
|
||||
await _fetchReleasesAndAssets();
|
||||
await _fetchReleasesAndAssets(canBeEmpty);
|
||||
}
|
||||
return _releases!;
|
||||
}
|
||||
|
||||
Future<List<ReleaseAsset>?> getLatestReleaseAssets() async {
|
||||
if (_releases == null) {
|
||||
await _fetchReleasesAndAssets();
|
||||
await _fetchReleasesAndAssets(false);
|
||||
}
|
||||
return _latestReleaseAssets;
|
||||
}
|
||||
|
||||
Future<void> _fetchReleasesAndAssets() async {
|
||||
Future<void> _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<dynamic>(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<List<ReleaseAsset>?> _parseReleaseAssets(List assetMaps) async {
|
||||
Future<List<ReleaseAsset>?> _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<String> _readSha(String shaUrl) async {
|
||||
return (await http.get(Uri.parse(shaUrl))).body.split(' ').first;
|
||||
}
|
||||
Future<String> _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<Icon> icons;
|
||||
|
||||
@@ -202,7 +211,7 @@ class FlatpakMeta {
|
||||
required List<Release>? localReleases,
|
||||
required List<ReleaseAsset>? 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<List<Release>> getReleases(bool fetchReleasesFromGithub) async {
|
||||
Future<List<Release>> getReleases(
|
||||
bool fetchReleasesFromGithub, String? addedTodaysVersion) async {
|
||||
final releases = List<Release>.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<List<ReleaseAsset>?> getReleaseAssets(bool fetchReleasesFromGithub) async {
|
||||
Future<List<ReleaseAsset>?> 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');
|
||||
}
|
||||
|
||||
@@ -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<String> arguments) async {
|
||||
if (Platform.isWindows) {
|
||||
throw Exception('Must be run under a UNIX-like operating system.');
|
||||
@@ -14,7 +26,8 @@ void main(List<String> 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<String> 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<String> 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<String?> 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<String>.empty(growable: true);
|
||||
for (final e in onlyArchListInput.entries) {
|
||||
@@ -131,7 +149,8 @@ class FlatpakManifestGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
void _lazyGenerateArchSupportMap(bool fetchFromGithub, List<ReleaseAsset> assets) {
|
||||
void _lazyGenerateArchSupportMap(
|
||||
bool fetchFromGithub, List<ReleaseAsset> assets) {
|
||||
if (fetchFromGithub) {
|
||||
if (_githubArchSupport == null) {
|
||||
_githubArchSupport = <CPUArchitecture, bool>{
|
||||
|
||||
@@ -13,18 +13,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.18.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.5"
|
||||
version: "0.13.6"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -37,10 +37,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.11.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -77,9 +77,9 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.2"
|
||||
sdks:
|
||||
dart: ">=2.18.5 <4.0.0"
|
||||
dart: ">=2.19.0 <4.0.0"
|
||||
|
||||
@@ -31,8 +31,7 @@ class GalleryProvider extends WgerBaseProvider with ChangeNotifier {
|
||||
|
||||
List<gallery.Image> images = [];
|
||||
|
||||
GalleryProvider(super.auth, List<gallery.Image> entries, [super.client])
|
||||
: images = entries;
|
||||
GalleryProvider(super.auth, List<gallery.Image> entries, [super.client]) : images = entries;
|
||||
|
||||
/// Clears all lists
|
||||
void clear() {
|
||||
|
||||
@@ -18,41 +18,39 @@ class PreviewExerciseImages extends StatelessWidget with ExerciseImagePickerMixi
|
||||
return SizedBox(
|
||||
height: 300,
|
||||
child: ListView(scrollDirection: Axis.horizontal, children: [
|
||||
...selectedImages
|
||||
.map(
|
||||
(file) => SizedBox(
|
||||
height: 200,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.file(file),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: IconButton(
|
||||
iconSize: 20,
|
||||
onPressed: () =>
|
||||
context.read<AddExerciseProvider>().removeExercise(file.path),
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
...selectedImages.map(
|
||||
(file) => SizedBox(
|
||||
height: 200,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.file(file),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: IconButton(
|
||||
iconSize: 20,
|
||||
onPressed: () =>
|
||||
context.read<AddExerciseProvider>().removeExercise(file.path),
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
|
||||
@@ -28,44 +28,37 @@ class Step2Variations extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
// Exercise bases with variations
|
||||
...exerciseProvider.exerciseBasesByVariation.keys
|
||||
.map(
|
||||
(key) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
//mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
...exerciseProvider.exerciseBasesByVariation[key]!
|
||||
.map(
|
||||
(base) => Text(
|
||||
base
|
||||
.getExercise(
|
||||
Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
,
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
...exerciseProvider.exerciseBasesByVariation.keys.map(
|
||||
(key) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
//mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
...exerciseProvider.exerciseBasesByVariation[key]!.map(
|
||||
(base) => Text(
|
||||
base
|
||||
.getExercise(Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.variationId == key,
|
||||
onChanged: (state) => provider.variationId = key),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
,
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.variationId == key,
|
||||
onChanged: (state) => provider.variationId = key),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Exercise bases without variations
|
||||
...exerciseProvider.bases
|
||||
.where((b) => b.variationId == null)
|
||||
.map(
|
||||
...exerciseProvider.bases.where((b) => b.variationId == null).map(
|
||||
(base) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -92,8 +85,7 @@ class Step2Variations extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -250,27 +250,25 @@ class _DashboardCalendarWidgetState extends State<DashboardCalendarWidget>
|
||||
valueListenable: _selectedEvents,
|
||||
builder: (context, value, _) => Column(
|
||||
children: [
|
||||
...value
|
||||
.map((event) => ListTile(
|
||||
title: Text((() {
|
||||
switch (event.type) {
|
||||
case EventType.caloriesDiary:
|
||||
return AppLocalizations.of(context).nutritionalDiary;
|
||||
...value.map((event) => ListTile(
|
||||
title: Text((() {
|
||||
switch (event.type) {
|
||||
case EventType.caloriesDiary:
|
||||
return AppLocalizations.of(context).nutritionalDiary;
|
||||
|
||||
case EventType.session:
|
||||
return AppLocalizations.of(context).workoutSession;
|
||||
case EventType.session:
|
||||
return AppLocalizations.of(context).workoutSession;
|
||||
|
||||
case EventType.weight:
|
||||
return AppLocalizations.of(context).weight;
|
||||
case EventType.weight:
|
||||
return AppLocalizations.of(context).weight;
|
||||
|
||||
case EventType.measurement:
|
||||
return AppLocalizations.of(context).measurement;
|
||||
}
|
||||
})()),
|
||||
subtitle: Text(event.description),
|
||||
//onTap: () => print('$event tapped!'),
|
||||
))
|
||||
|
||||
case EventType.measurement:
|
||||
return AppLocalizations.of(context).measurement;
|
||||
}
|
||||
})()),
|
||||
subtitle: Text(event.description),
|
||||
//onTap: () => print('$event tapped!'),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -341,12 +341,9 @@ class MuscleWidget extends StatelessWidget {
|
||||
return Stack(
|
||||
children: [
|
||||
SvgPicture.asset('assets/images/muscles/$background.svg'),
|
||||
...muscles
|
||||
.map((m) => SvgPicture.asset('assets/images/muscles/main/muscle-${m.id}.svg'))
|
||||
,
|
||||
...muscles.map((m) => SvgPicture.asset('assets/images/muscles/main/muscle-${m.id}.svg')),
|
||||
...musclesSecondary
|
||||
.map((m) => SvgPicture.asset('assets/images/muscles/secondary/muscle-${m.id}.svg'))
|
||||
,
|
||||
.map((m) => SvgPicture.asset('assets/images/muscles/secondary/muscle-${m.id}.svg')),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class NutritionalDiaryChartWidgetFl extends StatefulWidget {
|
||||
const NutritionalDiaryChartWidgetFl({
|
||||
super.key,
|
||||
required NutritionalPlan nutritionalPlan,
|
||||
}) : _nutritionalPlan = nutritionalPlan;
|
||||
}) : _nutritionalPlan = nutritionalPlan;
|
||||
|
||||
final NutritionalPlan _nutritionalPlan;
|
||||
|
||||
@@ -397,7 +397,7 @@ class FlNutritionalDiaryChartWidget extends StatefulWidget {
|
||||
const FlNutritionalDiaryChartWidget({
|
||||
super.key,
|
||||
required NutritionalPlan nutritionalPlan,
|
||||
}) : _nutritionalPlan = nutritionalPlan;
|
||||
}) : _nutritionalPlan = nutritionalPlan;
|
||||
|
||||
final Color barColor = Colors.red;
|
||||
final Color touchedBarColor = Colors.deepOrange;
|
||||
|
||||
@@ -131,16 +131,14 @@ class _WorkoutDayWidgetState extends State<WorkoutDayWidget> {
|
||||
child: Column(
|
||||
children: [
|
||||
if (set.comment != '') MutedText(set.comment),
|
||||
...set.settingsFiltered
|
||||
.map(
|
||||
(setting) => SettingWidget(
|
||||
set: set,
|
||||
setting: setting,
|
||||
expanded: _expanded,
|
||||
toggle: _toggleExpanded,
|
||||
),
|
||||
)
|
||||
,
|
||||
...set.settingsFiltered.map(
|
||||
(setting) => SettingWidget(
|
||||
set: set,
|
||||
setting: setting,
|
||||
expanded: _expanded,
|
||||
toggle: _toggleExpanded,
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -124,23 +124,21 @@ class _DayLogWidgetState extends State<DayLogWidget> {
|
||||
)
|
||||
else
|
||||
Container(),
|
||||
...widget._exerciseData[base]!
|
||||
.map(
|
||||
(log) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(log.singleLogRepTextNoNl),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showDeleteDialog(
|
||||
context, exercise.name, log, exercise, widget._exerciseData);
|
||||
},
|
||||
),
|
||||
],
|
||||
...widget._exerciseData[base]!.map(
|
||||
(log) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(log.singleLogRepTextNoNl),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showDeleteDialog(
|
||||
context, exercise.name, log, exercise, widget._exerciseData);
|
||||
},
|
||||
),
|
||||
)
|
||||
,
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ExerciseLogChart(base, widget._date),
|
||||
|
||||
Reference in New Issue
Block a user