From 3b31439c2e01d2d41ddd09a7846bb3da07c33428 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 12 Jul 2020 14:11:54 +0900 Subject: [PATCH] CI: release on tag with Github Actions --- .github/workflows/main.yml | 56 ++++++++++++++++++ android/app/build.gradle | 10 ++++ lib/flutter_version.dart | 10 ++++ lib/widgets/about/about_page.dart | 98 +++++++++++++++++++++++-------- lib/widgets/common/link_chip.dart | 57 ++++++++++-------- pubspec.lock | 7 +++ pubspec.yaml | 1 + scripts/update_flutter_version.sh | 10 ++++ 8 files changed, 198 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 lib/flutter_version.dart create mode 100755 scripts/update_flutter_version.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..b80da57b5 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,56 @@ +name: Release an APK and an App Bundle on tagging + +on: + push: + tags: + - v* + +jobs: + build: + name: Build and release artifacts. + runs-on: ubuntu-latest + steps: + - uses: actions/setup-java@v1 + with: + java-version: '11.x' + + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + + - name: Clone the repository. + uses: actions/checkout@v2 + + - name: Get packages for the Flutter project. + run: flutter pub get + + - name: Update the flutter version file. + working-directory: ${{ github.workspace }}/scripts + run: ./update_flutter_version.sh + + - name: Run the unit tests. + run: flutter test + + - name: Build signed artifacts. + # `KEY_JKS` should contain the result of: + # gpg -c --armor keystore.jks + # `KEY_JKS_PASSPHRASE` should contain the passphrase used for the command above + run: | + echo "${{ secrets.KEY_JKS }}" > release.keystore.asc + gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE + rm release.keystore.asc + flutter build apk + flutter build appbundle + rm $AVES_STORE_FILE + env: + AVES_STORE_FILE: ${{ github.workspace }}/key.jks + AVES_STORE_PASSWORD: ${{ secrets.AVES_STORE_PASSWORD }} + AVES_KEY_ALIAS: ${{ secrets.AVES_KEY_ALIAS }} + AVES_KEY_PASSWORD: ${{ secrets.AVES_KEY_PASSWORD }} + AVES_GOOGLE_API_KEY: ${{ secrets.AVES_GOOGLE_API_KEY }} + + - name: Create a release with the APK and App Bundle. + uses: ncipollo/release-action@v1 + with: + artifacts: "build/app/outputs/apk/release/*.apk,build/app/outputs/bundle/release/*.aab" + token: ${{ secrets.RELEASE_WORKFLOW_TOKEN }} diff --git a/android/app/build.gradle b/android/app/build.gradle index 043a73751..c53db25ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -27,7 +27,17 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { + // for release using credentials stored in a local file keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} else { + // for release using credentials in environment variables set up by Github Actions + // warning: in property file, single quotes should be escaped with a backslash + // but they should not be escaped when stored in env variables + keystoreProperties['storeFile'] = System.getenv('AVES_STORE_FILE') + keystoreProperties['storePassword'] = System.getenv('AVES_STORE_PASSWORD') + keystoreProperties['keyAlias'] = System.getenv('AVES_KEY_ALIAS') + keystoreProperties['keyPassword'] = System.getenv('AVES_KEY_PASSWORD') + keystoreProperties['googleApiKey'] = System.getenv('AVES_GOOGLE_API_KEY') } android { diff --git a/lib/flutter_version.dart b/lib/flutter_version.dart new file mode 100644 index 000000000..54ce29eac --- /dev/null +++ b/lib/flutter_version.dart @@ -0,0 +1,10 @@ +// run `scripts/update_flutter_version.sh` to update with the content of `flutter --version --machine` +const Map version = { + 'channel': 'unknown', + 'dartSdkVersion': 'unknown', + 'engineRevision': 'unknown', + 'frameworkCommitDate': 'unknown', + 'frameworkRevision': 'unknown', + 'frameworkVersion': 'unknown', + 'repositoryUrl': 'unknown', +}; diff --git a/lib/widgets/about/about_page.dart b/lib/widgets/about/about_page.dart index 6777a7257..a0708ee8b 100644 --- a/lib/widgets/about/about_page.dart +++ b/lib/widgets/about/about_page.dart @@ -1,7 +1,10 @@ +import 'package:aves/flutter_version.dart'; import 'package:aves/widgets/about/licenses.dart'; +import 'package:aves/widgets/common/aves_logo.dart'; import 'package:aves/widgets/common/link_chip.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:package_info/package_info.dart'; class AboutPage extends StatelessWidget { @override @@ -19,31 +22,8 @@ class AboutPage extends StatelessWidget { sliver: SliverList( delegate: SliverChildListDelegate( [ - Center( - child: Column( - children: [ - Text.rich( - TextSpan( - children: [ - const TextSpan(text: 'Made with ❤️ and '), - WidgetSpan( - child: FlutterLogo( - size: Theme.of(context).textTheme.bodyText2.fontSize * 1.25, - ), - ), - ], - ), - ), - const SizedBox(height: 8), - const LinkChip( - text: 'Sources', - url: 'https://github.com/deckerst/aves', - textStyle: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - ), - const SizedBox(height: 8), + AppReference(), + const SizedBox(height: 16), const Divider(), ], ), @@ -57,3 +37,71 @@ class AboutPage extends StatelessWidget { ); } } + +class AppReference extends StatefulWidget { + @override + _AppReferenceState createState() => _AppReferenceState(); +} + +class _AppReferenceState extends State { + Future packageInfoLoader; + + @override + void initState() { + super.initState(); + packageInfoLoader = PackageInfo.fromPlatform(); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + children: [ + _buildAvesLine(), + _buildFlutterLine(), + ], + ), + ); + } + + Widget _buildAvesLine() { + final textTheme = Theme.of(context).textTheme; + final style = textTheme.headline6.copyWith(fontWeight: FontWeight.bold); + + return FutureBuilder( + future: packageInfoLoader, + builder: (context, snapshot) { + return LinkChip( + leading: AvesLogo( + size: style.fontSize * 1.25, + ), + text: 'Aves ${snapshot.data?.version}', + url: 'https://github.com/deckerst/aves', + textStyle: style, + ); + }, + ); + } + + Widget _buildFlutterLine() { + final style = DefaultTextStyle.of(context).style; + final subColor = style.color.withOpacity(.6); + + return Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsetsDirectional.only(end: 4), + child: FlutterLogo( + size: style.fontSize * 1.25, + ), + ), + ), + TextSpan(text: 'Flutter ${version['frameworkVersion']}'), + ], + ), + style: TextStyle(color: subColor), + ); + } +} diff --git a/lib/widgets/common/link_chip.dart b/lib/widgets/common/link_chip.dart index 5a50dee38..bddceacdf 100644 --- a/lib/widgets/common/link_chip.dart +++ b/lib/widgets/common/link_chip.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class LinkChip extends StatelessWidget { + final Widget leading; final String text; final String url; final Color color; @@ -12,6 +13,7 @@ class LinkChip extends StatelessWidget { const LinkChip({ Key key, + this.leading, @required this.text, @required this.url, this.color, @@ -20,32 +22,35 @@ class LinkChip extends StatelessWidget { @override Widget build(BuildContext context) { - final effectiveTextStyle = (textStyle ?? DefaultTextStyle.of(context).style).copyWith( - color: color, - ); - return InkWell( - borderRadius: borderRadius, - onTap: () async { - if (await canLaunch(url)) { - await launch(url); - } - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - text, - style: effectiveTextStyle, - ), - const SizedBox(width: 8), - Icon( - AIcons.openInNew, - size: Theme.of(context).textTheme.bodyText2.fontSize, - color: color, - ) - ], + return DefaultTextStyle.merge( + style: (textStyle ?? const TextStyle()).copyWith(color: color), + child: InkWell( + borderRadius: borderRadius, + onTap: () async { + if (await canLaunch(url)) { + await launch(url); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (leading != null) ...[ + leading, + const SizedBox(width: 8), + ], + Text(text), + const SizedBox(width: 8), + Builder( + builder: (context) => Icon( + AIcons.openInNew, + size: DefaultTextStyle.of(context).style.fontSize, + color: color, + ), + ), + ], + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 813cb8ea0..4df3672d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -260,6 +260,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.1" + package_info: + dependency: "direct main" + description: + name: package_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" palette_generator: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9a772708a..42e1a200c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: google_maps_flutter: intl: outline_material_icons: + package_info: palette_generator: pdf: pedantic: diff --git a/scripts/update_flutter_version.sh b/scripts/update_flutter_version.sh new file mode 100755 index 000000000..55efd68ca --- /dev/null +++ b/scripts/update_flutter_version.sh @@ -0,0 +1,10 @@ +#!/bin/bash +FILE_PATH="../lib/flutter_version.dart" +rm "$FILE_PATH" +echo "Updating flutter_version.dart:" +{ + echo "const Map version = " + flutter --version --machine + echo ";" +} >> "$FILE_PATH" +cat "$FILE_PATH"