mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Resources for Flatpak generation
This commit is contained in:
8
flatpak/README
Normal file
8
flatpak/README
Normal file
@@ -0,0 +1,8 @@
|
||||
To generate a release:
|
||||
|
||||
1. Build the Linux release using Flutter.
|
||||
2. Add the new release version and date to the start of the "releases" list in the "spec.json" file (and adjust other parameters in the file if needed).
|
||||
3. Run "dart flatpak_generator.dart spec.json" in this folder.
|
||||
4. Upload the generated tar.gz file as a Github release, using the app's version name for the tag (e.g. "1.0.0").
|
||||
5. Test the Flatpak using the guide at https://docs.flatpak.org/en/latest/first-build.html, using the generated json manifest as your Flatpak manifest.
|
||||
6. Update your Flathub manifest file in your Flathub Github repo. (If the "flathub.json" file is not there yet, upload that too.)
|
||||
72
flatpak/de.wger.flutter.appdata.xml
Executable file
72
flatpak/de.wger.flutter.appdata.xml
Executable file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>de.wger.flutter</id>
|
||||
<name>wger</name>
|
||||
<summary>Fitness/workout, nutrition and weight tracker</summary>
|
||||
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>AGPL-3.0-or-later</project_license>
|
||||
<recommends>
|
||||
<control>touch</control>
|
||||
<control>pointing</control>
|
||||
<control>keyboard</control>
|
||||
</recommends>
|
||||
<developer_name>wger</developer_name>
|
||||
<url type="homepage">https://wger.de/</url>
|
||||
<url type="bugtracker">https://github.com/wger-project/flutter/issues</url>
|
||||
|
||||
<custom>
|
||||
<value key="Purism::form_factor">workstation</value>
|
||||
<value key="Purism::form_factor">mobile</value>
|
||||
</custom>
|
||||
|
||||
<description>
|
||||
<p>From fitness lovers to fitness lovers – get your health organized with WGER, your Workout Manager!</p>
|
||||
<p>Have you already found your #1 fitness app and do you love to create your own sports routines? No matter what type of sporty beast you are – we all have something in common: We love to keep track of our health data <3</p>
|
||||
<p>So we don’t judge you for still managing your fitness journey with your handy little workout log book but welcome to 2021!</p>
|
||||
<p>We have developed a 100% free digital health and fitness tracker app for you, sized down to the most relevant features to make your life easier. Get started, keep training and celebrate your progress!</p>
|
||||
<p>wger is an Open Source project and all about:</p>
|
||||
<ul>
|
||||
<li>Your Body</li>
|
||||
<li>Your Workouts</li>
|
||||
<li>Your Progress</li>
|
||||
<li>Your Data</li>
|
||||
</ul>
|
||||
<p>Your Body:
|
||||
No need to google for the ingredients of your favourite treats – choose your daily meals from more than 78000 products and see the nutritional values. Add meals to the nutritional plan and keep an overview of your diet in the calendar.</p>
|
||||
<p>Your Workouts:
|
||||
You know what is best for your body. Create your own workouts out of a growing variety from 200 different exercises. Then, use the Gym Mode to guide you through the training while you log your weights with one tap.</p>
|
||||
<p>Your Progress:
|
||||
Never lose sight of your goals. Track your weight and keep your statistics.</p>
|
||||
<p>Your Data:
|
||||
wger is your personalized fitness diary – but you own your data. Use the REST API to access and do amazing things with it.</p>
|
||||
<p>Please note: This free app is not based on additional fundings and we don’t ask you to donate money. More than that it is a community project which is growing constantly. So be prepared for new features anytime!</p>
|
||||
<h2 id="opensource-what-does-that-mean-">OpenSource – what does that mean?</h2>
|
||||
<p>Open Source means that the whole source code for this app and the server it talks to is free and available to anybody:</p>
|
||||
<ul>
|
||||
<li>Do you want to run wger on your own server for you or your local gym? Go ahead!</li>
|
||||
<li>Do you miss a feature and want to implement it? Start now!</li>
|
||||
<li>Do you want to check that nothing is being sent anywhere? You can!</li>
|
||||
</ul>
|
||||
<p>Join our community and become a part of sport enthusiasts and IT geeks from all over the world. We keep working on adjusting and optimizing the app customized to our needs. We love your input so feel free to jump in anytime and contribute your wishes and ideas!</p>
|
||||
<ul>
|
||||
<li>find the source code on <a href="https://github.com/wger-project">https://github.com/wger-project</a></li>
|
||||
<li>ask your questions or just say hello on our discord Server <a href="https://discord.gg/rPWFv6W">https://discord.gg/rPWFv6W</a></li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>wger's dashboard</caption>
|
||||
<image type="source">https://github.com/wger-project/flutter/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/01%20-%20dashboard.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="1.5.3" date="2023-03-16" />
|
||||
</releases>
|
||||
|
||||
<content_rating type="oars-1.1" />
|
||||
|
||||
<launchable type="desktop-id">de.wger.flutter.desktop</launchable>
|
||||
</component>
|
||||
11
flatpak/de.wger.flutter.desktop
Executable file
11
flatpak/de.wger.flutter.desktop
Executable file
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=wger
|
||||
Comment=Fitness/workout, nutrition and weight tracker
|
||||
Categories=Education;Utility;Sports;
|
||||
Icon=de.wger.flutter
|
||||
Exec=wger
|
||||
StartupWMClass=wger
|
||||
X-Purism-FormFactor=Workstation;Mobile;
|
||||
X-KDE-FormFactors=desktop;tablet;handset;mediacenter;
|
||||
305
flatpak/flatpak_generator.dart
Normal file
305
flatpak/flatpak_generator.dart
Normal file
@@ -0,0 +1,305 @@
|
||||
// ignore_for_file: avoid_classes_with_only_static_members, avoid_print
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
void main(List<String> arguments) {
|
||||
if (arguments.length != 1) {
|
||||
throw Exception('Must have only one argument: the path to the JSON specification.');
|
||||
}
|
||||
if (!Platform.isLinux) {
|
||||
throw Exception('Must be run under x86_64 Linux');
|
||||
}
|
||||
final jsonFile = File(arguments[0]);
|
||||
if (!jsonFile.existsSync()) {
|
||||
throw Exception('The provided JSON file does not exist.');
|
||||
}
|
||||
final specJson = SpecJson.fromJson(jsonFile);
|
||||
|
||||
final outputDir = Directory.current;
|
||||
|
||||
final packageGenerator = PackageGenerator(inputDir: jsonFile.parent, specJson: specJson);
|
||||
packageGenerator.generatePackage(Directory.current, _Platform.x86_64);
|
||||
|
||||
if (specJson.linuxArmReleaseBundleDirPath != null) {
|
||||
packageGenerator.generatePackage(Directory.current, _Platform.aarch64);
|
||||
}
|
||||
|
||||
final sha256x64 = packageGenerator.sha256x64;
|
||||
|
||||
if (sha256x64 == null) {
|
||||
throw Exception('Could not generate SHA256 for the created package');
|
||||
}
|
||||
|
||||
final sha256aarch64 = packageGenerator.sha256aarch64;
|
||||
|
||||
final manifestContent =
|
||||
FlatpakManifestGenerator(specJson).getFlatpakManifest(sha256x64, sha256aarch64);
|
||||
final manifestPath = '${outputDir.path}/${specJson.appId}.json';
|
||||
final manifestFile = File(manifestPath);
|
||||
manifestFile.writeAsStringSync(manifestContent);
|
||||
print('Generated $manifestPath');
|
||||
|
||||
if (specJson.linuxArmReleaseBundleDirPath == null) {
|
||||
final flathubJsonPath = '${outputDir.path}/${FlathubJsonGenerator.filename}';
|
||||
final flathubJsonFile = File(flathubJsonPath);
|
||||
flathubJsonFile.writeAsStringSync(FlathubJsonGenerator.generate());
|
||||
print('Generated $flathubJsonPath');
|
||||
}
|
||||
}
|
||||
|
||||
enum _Platform { x86_64, aarch64 }
|
||||
|
||||
class PackageGenerator {
|
||||
final Directory inputDir;
|
||||
final SpecJson specJson;
|
||||
String? sha256x64;
|
||||
String? sha256aarch64;
|
||||
|
||||
PackageGenerator({required this.inputDir, required this.specJson});
|
||||
|
||||
void generatePackage(Directory outputDir, _Platform platform) {
|
||||
final tempDir = outputDir.createTempSync('flutter_package_generator');
|
||||
final appId = specJson.appId;
|
||||
|
||||
// desktop file
|
||||
final desktopFile = File('${inputDir.path}/${specJson.desktopPath}');
|
||||
|
||||
if (!desktopFile.existsSync()) {
|
||||
throw Exception(
|
||||
'The desktop file does not exist under the specified path: ${desktopFile.path}');
|
||||
}
|
||||
|
||||
desktopFile.copySync(
|
||||
'${tempDir.path}/$appId.desktop'); //todo does "$appName" have to be in the path too?
|
||||
|
||||
// icons
|
||||
final iconTempDir = Directory('${tempDir.path}/icons');
|
||||
|
||||
for (final icon in specJson.icons) {
|
||||
final iconFile = File('${inputDir.path}/${icon.path}');
|
||||
if (!iconFile.existsSync()) {
|
||||
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}/$appId.${icon.fileExtension}');
|
||||
}
|
||||
|
||||
// appdata file
|
||||
final origAppDataFile = File('${inputDir.path}/${specJson.appDataPath}');
|
||||
if (!origAppDataFile.existsSync()) {
|
||||
throw Exception(
|
||||
'The app data file does not exist under the specified path: ${origAppDataFile.path}');
|
||||
}
|
||||
|
||||
final editedAppDataContent =
|
||||
AppDataModifier.replaceVersions(origAppDataFile.readAsStringSync(), specJson.releases);
|
||||
|
||||
final editedAppDataFile = File('${tempDir.path}/$appId.appdata.xml');
|
||||
editedAppDataFile.writeAsStringSync(editedAppDataContent);
|
||||
|
||||
// build files
|
||||
final bundlePath = platform == _Platform.aarch64
|
||||
? specJson.linuxArmReleaseBundleDirPath
|
||||
: specJson.linuxReleaseBundleDirPath;
|
||||
final buildDir = Directory(bundlePath!);
|
||||
if (!buildDir.existsSync()) {
|
||||
throw Exception(
|
||||
'The linux build directory does not exist under the specified path: ${buildDir.path}');
|
||||
}
|
||||
final destDir = Directory('${tempDir.path}/bin');
|
||||
destDir.createSync();
|
||||
|
||||
final platformSuffix = platform == _Platform.aarch64 ? 'aarch64' : 'x86_64';
|
||||
final packagePath =
|
||||
'${outputDir.absolute.path}/${specJson.lowercaseAppName}-linux-$platformSuffix.tar.gz';
|
||||
|
||||
Process.runSync('cp', [
|
||||
'-r',
|
||||
'${buildDir.absolute.path}/.',
|
||||
destDir.absolute.path
|
||||
]); //todo test with spaces in name
|
||||
Process.runSync('tar', ['-czvf', packagePath, '.'],
|
||||
workingDirectory: tempDir.absolute.path); //todo test with spaces in name
|
||||
|
||||
print('Generated $packagePath');
|
||||
|
||||
final preShasum = Process.runSync('shasum', ['-a', '256', packagePath]);
|
||||
|
||||
if (platform == _Platform.aarch64) {
|
||||
sha256aarch64 = preShasum.stdout.toString().split(' ').first;
|
||||
} else {
|
||||
sha256x64 = preShasum.stdout.toString().split(' ').first;
|
||||
}
|
||||
|
||||
tempDir.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// updates releases in ${appName}.appdata.xml
|
||||
class AppDataModifier {
|
||||
static String replaceVersions(String origAppDataContent, List<Release> versions) {
|
||||
final joinedReleases =
|
||||
versions.map((v) => '<release version="${v.version}" date="${v.date}">').join('\n');
|
||||
final releasesSection = '<releases>\n$joinedReleases\n</releases>';
|
||||
if (origAppDataContent.contains('<releases')) {
|
||||
return origAppDataContent.replaceFirst(
|
||||
RegExp('<releases.*</releases>', multiLine: true), releasesSection);
|
||||
} else {
|
||||
return origAppDataContent.replaceFirst('</component>', '$releasesSection\n</component>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ${appId}.json
|
||||
class FlatpakManifestGenerator {
|
||||
final SpecJson specJson;
|
||||
|
||||
FlatpakManifestGenerator(this.specJson);
|
||||
|
||||
String getFlatpakManifest(String sha256x64, String? sha256aarch64) {
|
||||
final appName = specJson.lowercaseAppName;
|
||||
final appId = specJson.appId;
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
return encoder.convert({
|
||||
'app-id': appId,
|
||||
'runtime': 'org.freedesktop.Platform',
|
||||
'runtime-version': specJson.runtimeVersion,
|
||||
'sdk': 'org.freedesktop.Sdk',
|
||||
'command': appName,
|
||||
'separate-locales': false,
|
||||
'finish-args': specJson.finishArgs,
|
||||
'modules': [
|
||||
...specJson.extraModules ?? [],
|
||||
{
|
||||
'name': appName,
|
||||
'buildsystem': 'simple',
|
||||
'build-commands': [
|
||||
'cp -R $appName/bin/ /app/$appName',
|
||||
'chmod +x /app/$appName/$appName',
|
||||
'mkdir /app/bin/',
|
||||
'mkdir /app/lib/',
|
||||
'ln -s /app/$appName/$appName /app/bin/$appName',
|
||||
...specJson.flatpakCommandsAfterUnpack ?? [],
|
||||
...specJson.icons.map((icon) =>
|
||||
'install -Dm644 $appName/icons/${icon.type}/$appId.${icon.fileExtension} /app/share/icons/hicolor/${icon.type}/apps/$appId.${icon.fileExtension}'), //TODO THIS DOES NOT ACCOUNT FOR THE symbolic icon name!!!
|
||||
'install -Dm644 $appName/$appId.desktop /app/share/applications/$appId.desktop',
|
||||
'install -Dm644 $appName/$appId.appdata.xml /app/share/applications/$appId.appdata.xml'
|
||||
],
|
||||
'sources': [
|
||||
{
|
||||
'type': 'archive',
|
||||
'only-arches': ['x86_64'],
|
||||
'url':
|
||||
'https://github.com/${specJson.githubReleaseOrganization}/${specJson.githubReleaseProject}/releases/download/${specJson.releases.first.version}/${specJson.lowercaseAppName}-linux-x86_64.tar.gz',
|
||||
'sha256': sha256x64,
|
||||
'dest': specJson.lowercaseAppName
|
||||
},
|
||||
if (specJson.linuxArmReleaseBundleDirPath != null)
|
||||
{
|
||||
'type': 'archive',
|
||||
'only-arches': ['aarch64'],
|
||||
'url':
|
||||
'https://github.com/${specJson.githubReleaseOrganization}/${specJson.githubReleaseProject}/releases/download/${specJson.releases.first.version}/${specJson.lowercaseAppName}-linux-aarch64.tar.gz',
|
||||
'sha256': sha256aarch64,
|
||||
'dest': specJson.lowercaseAppName
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// flathub.json
|
||||
class FlathubJsonGenerator {
|
||||
static const String filename = 'flathub.json';
|
||||
|
||||
static String generate() {
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
return encoder.convert({
|
||||
'only-arches': ['x86_64']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Release {
|
||||
final String version;
|
||||
final String date;
|
||||
|
||||
Release({required this.version, required this.date});
|
||||
}
|
||||
|
||||
class Icon {
|
||||
final String type;
|
||||
final String path;
|
||||
late final String fileExtension;
|
||||
|
||||
Icon({required this.type, required this.path}) {
|
||||
fileExtension = path.split('.').last;
|
||||
}
|
||||
}
|
||||
|
||||
class SpecJson {
|
||||
//todo allow extra modules
|
||||
final String appId;
|
||||
final String lowercaseAppName;
|
||||
final List<Release> releases;
|
||||
final String runtimeVersion;
|
||||
final String linuxReleaseBundleDirPath;
|
||||
final String? linuxArmReleaseBundleDirPath;
|
||||
final String appDataPath;
|
||||
final String desktopPath;
|
||||
final List<Icon> icons;
|
||||
final List<String>? flatpakCommandsAfterUnpack;
|
||||
final List<dynamic>? extraModules;
|
||||
final List<String> finishArgs;
|
||||
final String githubReleaseOrganization;
|
||||
final String githubReleaseProject;
|
||||
|
||||
SpecJson(
|
||||
{required this.appId,
|
||||
required this.lowercaseAppName,
|
||||
required this.releases,
|
||||
required this.runtimeVersion,
|
||||
required this.linuxReleaseBundleDirPath,
|
||||
this.linuxArmReleaseBundleDirPath,
|
||||
required this.appDataPath,
|
||||
required this.desktopPath,
|
||||
required this.icons,
|
||||
required this.flatpakCommandsAfterUnpack,
|
||||
this.extraModules,
|
||||
required this.finishArgs,
|
||||
required this.githubReleaseOrganization,
|
||||
required this.githubReleaseProject});
|
||||
|
||||
static SpecJson fromJson(File jsonFile) {
|
||||
try {
|
||||
final json = jsonDecode(jsonFile.readAsStringSync());
|
||||
return SpecJson(
|
||||
appId: json['appId'],
|
||||
lowercaseAppName: json['lowercaseAppName'],
|
||||
releases: (json['releases'] as List).map((r) {
|
||||
final rMap = r as Map;
|
||||
return Release(version: rMap['version'], date: rMap['date']);
|
||||
}).toList(),
|
||||
runtimeVersion: json['runtimeVersion'],
|
||||
linuxReleaseBundleDirPath: json['linuxReleaseBundleDirPath'],
|
||||
appDataPath: json['appDataPath'],
|
||||
desktopPath: json['desktopPath'],
|
||||
icons: (json['icons'] as Map).entries.map((mapEntry) {
|
||||
return Icon(type: mapEntry.key as String, path: mapEntry.value as String);
|
||||
}).toList(),
|
||||
flatpakCommandsAfterUnpack:
|
||||
(json['buildCommandsAfterUnpack'] as List?)?.map((bc) => bc as String)?.toList(),
|
||||
linuxArmReleaseBundleDirPath: json['linuxArmReleaseBundleDirPath'] as String?,
|
||||
extraModules: json['extraModules'] as List?,
|
||||
finishArgs: (json['finishArgs'] as List).map((fa) => fa as String).toList(),
|
||||
githubReleaseOrganization: json['githubReleaseOrganization'],
|
||||
githubReleaseProject: json['githubReleaseProject']);
|
||||
} catch (e) {
|
||||
throw Exception('Could not parse JSON file, due to this error:\n$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
flatpak/logo128.png
Normal file
BIN
flatpak/logo128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
flatpak/logo512.png
Normal file
BIN
flatpak/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
flatpak/logo64.png
Normal file
BIN
flatpak/logo64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
28
flatpak/spec.json
Normal file
28
flatpak/spec.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"appId": "de.wger.flutter",
|
||||
"releases": [
|
||||
{
|
||||
"version": "1.5.3",
|
||||
"date": "2023-03-16"
|
||||
}
|
||||
],
|
||||
"lowercaseAppName": "wger",
|
||||
"runtimeVersion": "22.08",
|
||||
"linuxReleaseBundleDirPath": "../build/linux/x64/release/bundle",
|
||||
"appDataPath": "de.wger.flutter.appdata.xml",
|
||||
"desktopPath": "de.wger.flutter.desktop",
|
||||
"icons": {
|
||||
"64x64": "logo64.png",
|
||||
"128x126": "logo128.png",
|
||||
"512x512": "logo512.png"
|
||||
},
|
||||
"finishArgs": [
|
||||
"--share=ipc",
|
||||
"--share=network",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--device=dri"
|
||||
],
|
||||
"githubReleaseOrganization": "wger-project",
|
||||
"githubReleaseProject": "flutter"
|
||||
}
|
||||
Reference in New Issue
Block a user