Skip to content
This repository has been archived by the owner on Jan 16, 2024. It is now read-only.

Commit

Permalink
feat: add auto-updater (#45)
Browse files Browse the repository at this point in the history
* feat!: test release

* ci: add better changelog to the releases

* feat: add auto-updater

* fix: unable to _getVersion

* fix: missing v

* Updated uptdat lib implementation

* build: add flutter_distributor to create install packages

* Add dist/ folder to gitignore

* Prepared for app distribution

* Prepare for auto updater release

* Remove unused dependencies

---------
[skip-ci]
Co-authored-by: 89pleasure <133201120+89pleasure@users.noreply.github.com>
  • Loading branch information
shiipou committed Jun 13, 2023
1 parent b03eb5c commit b58237e
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 54 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Expand Up @@ -131,9 +131,10 @@ jobs:
run: |
changelog_file=$(mktemp)
echo "${{ needs.sem-version.outputs.CHANGELOG }}" >> "$changelog_file"
ARGS="--repo ${{ github.repository }} --target ${{ github.sha }} -F $changelog_file"
if [[ "${{ needs.sem-version.outputs.IS_PRE_RELEASE }}" == "true" ]]; then
ARGS="$ARGS --prerelease"
fi
gh release create $ARGS v${{ needs.sem-version.outputs.VERSION }} ./artifacts/*/*
gh release create $ARGS v${{ needs.sem-version.outputs.VERSION }} ./artifacts/*/*
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -3,6 +3,7 @@

### ModMopet ##
dart-define.json
dist/

### Flutter ###
# Flutter/Dart/Pub related
Expand All @@ -15,6 +16,7 @@ dart-define.json
.pub-cache/
.pub/
build/
dist/
coverage/
lib/generated_plugin_registrant.dart
# For library packages, don’t commit the pubspec.lock file.
Expand Down
6 changes: 5 additions & 1 deletion lib/main.dart
Expand Up @@ -15,6 +15,8 @@ const String emulatorBoxName = 'emulator';

void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Init image cache
await EasyLocalization.ensureInitialized();
String imageCacheDirectory = '${(await getApplicationSupportDirectory()).path}${Platform.pathSeparator}image_cache';
await FastCachedImageConfig.init(subDir: imageCacheDirectory, clearCacheAfter: const Duration(days: 30));
Expand Down Expand Up @@ -56,6 +58,8 @@ void main() async {
const Locale('pt', 'PT')
];

String version = const String.fromEnvironment('FLUTTER_BUILD_NAME', defaultValue: 'v1.0.0');

// Sentry implementation
await SentryFlutter.init(
(options) {
Expand All @@ -68,7 +72,7 @@ void main() async {
EasyLocalization(
supportedLocales: supportedLocales,
path: 'assets/translations',
child: App(settingsController: settingsController),
child: App(version: version, settingsController: settingsController),
),
),
);
Expand Down
54 changes: 36 additions & 18 deletions lib/src/app.dart
@@ -1,7 +1,12 @@
import 'dart:io';

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:github/github.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:modmopet/src/service/github/github.dart';
import 'package:modmopet/src/widgets/mm_layout.dart';
import 'package:updat/updat_window_manager.dart';
import 'screen/settings/settings_controller.dart';
import 'themes/color_schemes.g.dart';

Expand All @@ -16,9 +21,11 @@ class SimplePageRoute<T> extends MaterialPageRoute<T> {
class App extends HookConsumerWidget {
const App({
super.key,
required this.version,
required this.settingsController,
});

final String version;
final SettingsController settingsController;

@override
Expand All @@ -29,39 +36,50 @@ class App extends HookConsumerWidget {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
// Providing a restorationScopeId allows the Navigator built by the
// MaterialApp to restore the navigation stack when a user leaves and
// returns to the app after it has been killed while running in the
// background.
restorationScopeId: 'modmopet',

// Provide the generated AppLocalizations to the MaterialApp. This
// allows descendant Widgets to display the correct translations
// depending on the user's locale.
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
title: 'ModMopet',
// Define a light and dark color theme. Then, read the user's
// preferred ThemeMode (light, dark, or system default) from the
// SettingsController to display the correct theme.
theme:
ThemeData(useMaterial3: true, colorScheme: darkColorScheme, dividerColor: MMColors.instance.background),
darkTheme:
ThemeData(useMaterial3: true, colorScheme: darkColorScheme, dividerColor: MMColors.instance.background),
themeMode: settingsController.themeMode,

// Define a function to handle named routes in order to support
// Flutter web url navigation and deep linking.
onGenerateRoute: (RouteSettings routeSettings) {
context.setLocale(const Locale('en', 'US'));
final modmopetSlug = RepositorySlug('modmopet', 'modmopet');
return SimplePageRoute<void>(
settings: routeSettings,
builder: (BuildContext context) {
return Material(
child: MMLayout(
settingsController: settingsController,
routeSettings: routeSettings,
return UpdatWindowManager(
appName: 'ModMopet',
getLatestVersion: () async {
final latestRelease = await GithubClient().getLatestReleaseBySlug(modmopetSlug);
return latestRelease.tagName?.substring(1); // remove v from tag
},
getBinaryUrl: (version) async {
final latestRelease = await GithubClient().getLatestReleaseBySlug(modmopetSlug);
List<ReleaseAsset>? assets = latestRelease.assets;
ReleaseAsset asset =
assets!.firstWhere((asset) => asset.name!.contains(Platform.operatingSystem));
return asset.browserDownloadUrl!;
},
getChangelog: (latestVersion, appVersion) async {
final ReleaseNotes releaseNotes =
await GithubClient().generateReleaseNotes('v$latestVersion', 'v$appVersion');

return '${releaseNotes.name}\n\n${releaseNotes.body}';
},
currentVersion: version,
openOnDownload: false,
closeOnInstall: true,
child: Material(
child: MMLayout(
version: version,
settingsController: settingsController,
routeSettings: routeSettings,
),
),
);
},
Expand Down
4 changes: 2 additions & 2 deletions lib/src/entity/git_source.dart
Expand Up @@ -75,7 +75,7 @@ Future<void> updateSources(UpdateSourcesRef ref) async {

Future<void> _doUpdateIteration(String gameTitleId, GitSource source, FutureProviderRef ref) async {
debugPrint('Check for new update');
final latestRelease = await GithubClient().getLatestRelease(source);
final latestRelease = await GithubClient().getLatestReleaseByGitSource(source);
final latestGithubReleaseId = latestRelease.id;
String? latestReleaseId = await PlatformFilesystem.instance.readFromLocal('latestReleaseId');

Expand Down Expand Up @@ -116,7 +116,7 @@ Future<void> _doDownloadAndSaveArchive(String gameTitleId, GitSource source) asy

// Download, extract, delete zipfile
LoggerService.instance.log('Update Manager: Downloading zipball...');
final latestRelease = await GithubClient().getLatestRelease(source);
final latestRelease = await GithubClient().getLatestReleaseByGitSource(source);

// Try to find asset by name, since we want the full release
final releaseAssets = latestRelease.assets;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/provider/game_list_provider.dart
Expand Up @@ -102,7 +102,7 @@ Future<void> _checkTitlesDatabase() async {

File titlesJsonFile = await PlatformFilesystem.instance.getFile('titlesdb.json');
final slug = RepositorySlug('arch-box', 'titledb');
final latestRelease = await GithubClient().getLatestTitleDBRelease(slug);
final latestRelease = await GithubClient().getLatestReleaseBySlug(slug);
final assets = latestRelease.assets;

if (assets != null && assets.isNotEmpty) {
Expand Down
15 changes: 4 additions & 11 deletions lib/src/screen/mods/mods_view.dart
Expand Up @@ -25,8 +25,7 @@ class ModsView extends HookConsumerWidget {

return Column(
children: [
MMBreadcrumbsBar('Games - ${game?.title} - Modifications',
routeName: GameListView.routeName),
MMBreadcrumbsBar('Games - ${game?.title} - Modifications', routeName: GameListView.routeName),
SizedBox(
height: 140.0,
width: double.infinity,
Expand Down Expand Up @@ -93,9 +92,7 @@ class ModsView extends HookConsumerWidget {
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
width: 1, color: MMColors.instance.backgroundBorder)),
border: Border.symmetric(horizontal: BorderSide(width: 1, color: MMColors.instance.backgroundBorder)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Expand Down Expand Up @@ -149,9 +146,7 @@ Widget createSourceMenu(BuildContext context, WidgetRef ref) {
createSourceDropdown(context, ref),
IconButton(
tooltip: 'Open Github',
onPressed: selectedSource != null
? () => _launchWebsite(selectedSource.uri)
: null,
onPressed: selectedSource != null ? () => _launchWebsite(selectedSource.uri) : null,
icon: const Icon(
Icons.open_in_browser_outlined,
size: 24.0,
Expand Down Expand Up @@ -183,9 +178,7 @@ Widget createSourceDropdown(BuildContext context, WidgetRef ref) {
final items = availableSources.map((source) {
return DropdownMenuItem<GitSource>(
value: source,
child: Text(source.repository,
style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis),
child: Text(source.repository, style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.ellipsis),
);
}).toList();

Expand Down
18 changes: 16 additions & 2 deletions lib/src/service/github/github.dart
@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:github/github.dart';
import 'package:modmopet/src/entity/git_source.dart';
import 'package:modmopet/src/service/github/auth.dart';
Expand All @@ -10,12 +11,25 @@ class GithubClient {
return GitHub(auth: Authentication.withToken(await const GithubAuthService().getToken()));
}

Future<Release> getLatestRelease(GitSource source) async {
Future<Release> getLatestReleaseByGitSource(GitSource source) async {
final repositorySlug = RepositorySlug(source.user, source.repository);
return (await github).repositories.getLatestRelease(repositorySlug);
}

Future<Release> getLatestTitleDBRelease(RepositorySlug slug) async {
Future<Release> getLatestReleaseBySlug(RepositorySlug slug) async {
return (await github).repositories.getLatestRelease(slug);
}

Future<ReleaseNotes> generateReleaseNotes(String latestVersion, String currentVersion) async {
final RepositorySlug slug = RepositorySlug('modmopet', 'modmopet');
debugPrint(latestVersion);
debugPrint(currentVersion);
CreateReleaseNotes releaseNotesFrom = CreateReleaseNotes(
slug.owner,
slug.name,
latestVersion,
previousTagName: currentVersion,
);
return await (await github).repositories.generateReleaseNotes(releaseNotesFrom);
}
}
17 changes: 17 additions & 0 deletions lib/src/widgets/mm_layout.dart
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:modmopet/src/entity/emulator.dart';
Expand All @@ -13,11 +15,13 @@ import 'package:modmopet/src/widgets/mm_draggable_appbar.dart';
import 'package:modmopet/src/widgets/mm_navigation_rail.dart';

class MMLayout extends HookConsumerWidget {
final String version;
final SettingsController settingsController;
final RouteSettings routeSettings;
const MMLayout({
required this.settingsController,
required this.routeSettings,
required this.version,
super.key,
});

Expand Down Expand Up @@ -110,3 +114,16 @@ class MMLayout extends HookConsumerWidget {
);
}
}

String get platformExt {
switch (Platform.operatingSystem) {
case 'windows':
return 'zip';
case 'macos':
return 'tar.gz';
case 'linux':
return 'tar.gz';
default:
return 'zip';
}
}
27 changes: 27 additions & 0 deletions linux/packaging/appimage/make_config.yaml
@@ -0,0 +1,27 @@
display_name: ModMopet

icon: assets/images/logo.png

keywords:
- mods
- manager
- switch
- emulator

generic_name: ModMopet

categories:
- Tools

startup_notify: true

# You can specify the shared libraries that you want to bundle with your app
#
# flutter_distributor automatically detects the shared libraries that your app
# depends on, but you can also specify them manually here.
#
# The following example shows how to bundle the libcurl library with your app.
#
# include:
# - libcurl.so.4
include: []
28 changes: 28 additions & 0 deletions linux/packaging/deb/make_config.yaml
@@ -0,0 +1,28 @@
display_name: ModMopet
package_name: modmopet
maintainer:
name: 89pleasure
email: 133201120+89pleasure@users.noreply.github.com
priority: optional
section: x11
installed_size: 6604
essential: false
icon: assets/logo.png

postinstall_scripts:
- echo "ModMopet has been successfully installed!"
postuninstall_scripts:
- echo "ModMopet has been successfully uninstalled. Bye!"

keywords:
- mods
- manager
- switch
- emulator

generic_name: ModMopet

categories:
- Tools

startup_notify: true
10 changes: 10 additions & 0 deletions macos/packaging/dmg/make_config.yaml
@@ -0,0 +1,10 @@
title: ModMopet
contents:
- x: 448
y: 344
type: link
path: "/Applications"
- x: 192
y: 344
type: file
path: ModMopet.app

0 comments on commit b58237e

Please sign in to comment.