Bläddra i källkod

feat: app bundle with icon

    * feat: bundle script
    * fix: added .env to assets
    * feat: added mobile test to main Makefile
    * feat: updated mobile app docs
    * fix: removed .env from assets
    * feat: run mobile unit tests in CI
    * feat: mobile Dockerfile
    * feat: flutter prod build
    * feat: updated app name
    * feat: app icon
Fela Maslen 4 år sedan
förälder
incheckning
ddb1029fd6
51 ändrade filer med 228 tillägg och 51 borttagningar
  1. 5 1
      Jenkinsfile
  2. 5 0
      Makefile
  3. 1 0
      README.md
  4. 6 0
      gmus-mobile/.dockerignore
  5. 1 1
      gmus-mobile/.env.example
  6. 33 0
      gmus-mobile/Dockerfile
  7. 33 0
      gmus-mobile/Makefile
  8. 3 14
      gmus-mobile/README.md
  9. 17 5
      gmus-mobile/android/app/build.gradle
  10. 3 2
      gmus-mobile/android/app/src/main/AndroidManifest.xml
  11. BIN
      gmus-mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  12. BIN
      gmus-mobile/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
  13. BIN
      gmus-mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  14. BIN
      gmus-mobile/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
  15. BIN
      gmus-mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  16. BIN
      gmus-mobile/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
  17. BIN
      gmus-mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  18. BIN
      gmus-mobile/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
  19. BIN
      gmus-mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  20. BIN
      gmus-mobile/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
  21. BIN
      gmus-mobile/assets/icon/icon.png
  22. 1 1
      gmus-mobile/ios/Runner.xcodeproj/project.pbxproj
  23. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  24. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  25. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  26. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  27. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  28. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  29. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  30. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  31. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  32. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  33. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  34. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  35. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  36. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  37. BIN
      gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  38. 2 2
      gmus-mobile/lib/components/albums.dart
  39. 3 2
      gmus-mobile/lib/components/artists.dart
  40. 0 1
      gmus-mobile/lib/components/content.dart
  41. 2 2
      gmus-mobile/lib/components/songs.dart
  42. 3 1
      gmus-mobile/lib/config.dart
  43. 1 1
      gmus-mobile/lib/main.dart
  44. 14 0
      gmus-mobile/lib/utils/url.dart
  45. 38 3
      gmus-mobile/pubspec.lock
  46. 10 11
      gmus-mobile/pubspec.yaml
  47. 14 0
      gmus-mobile/scripts/bundle.sh
  48. 24 0
      gmus-mobile/scripts/prepare_android.sh
  49. 2 2
      gmus-mobile/test/components/status_test.dart
  50. 2 2
      gmus-mobile/test/controller_test.dart
  51. 5 0
      k8s/deploy.sh

+ 5 - 1
Jenkinsfile

@@ -8,6 +8,7 @@ node {
     script {
       IMAGE_BACKEND = sh(returnStdout: true, script: "cd gmus-backend && make get_image").trim()
       IMAGE_WEB = sh(returnStdout: true, script: "cd gmus-web && make get_image").trim()
+      IMAGE_MOBILE = sh(returnStdout: true, script: "cd gmus-mobile && make get_image").trim()
     }
 
     stage('Build and push images') {
@@ -45,6 +46,9 @@ node {
               "gmus-web:unit tests": {
                 sh "docker run --rm -e 'CI=1' -e 'REACT_APP_API_URL=http://my-api.url:1234' docker.fela.space/gmus-web-builder:latest sh -c 'make test'"
               },
+              "gmus-mobile:unit tests": {
+                sh "docker run --rm ${IMAGE_MOBILE} sh -c 'flutter test'"
+              },
               "gmus-backend:tests": {
                 sh "docker run --rm --link ${pg.id}:db --link ${redis.id}:redis ${IMAGE_BACKEND} sh -c 'make test.ci'"
               }
@@ -56,7 +60,7 @@ node {
 
     stage('Deploy') {
       if (env.BRANCH_NAME == "master") {
-        sh 'LIBRARY_DIRECTORY=/data/user/music/ogg ./k8s/deploy.sh'
+        sh 'LIBRARY_DIRECTORY=$GMUS_LIBRARY_DIRECTORY ./k8s/deploy.sh'
       }
     }
   }

+ 5 - 0
Makefile

@@ -10,6 +10,7 @@ build:
 build.docker:
 	cd gmus-backend && make build.docker
 	cd gmus-web && make build.docker
+	cd gmus-mobile && make build.docker
 
 push:
 	cd gmus-backend && make push
@@ -21,6 +22,10 @@ test.backend:
 test.frontend.web:
 	cd gmus-web && yarn test
 
+test.frontend.mobile:
+	cd gmus-mobile && make test.flutter
+
 test:
 	make test.backend
 	make test.frontend.web
+	make test.frontend.mobile

+ 1 - 0
README.md

@@ -11,6 +11,7 @@ For more info, see the [readme](./gmus-backend/README.md).
 Each frontend implements the APIs provided by the backend. Their job is to play music, or control the master client. The following clients are implemented:
 
 - [gmus-web](./gmus-web/README.md) - web client
+- [gmus-mobile](./gmus-mobile/README.md) - mobile app
 
 ## Architecture
 

+ 6 - 0
gmus-mobile/.dockerignore

@@ -0,0 +1,6 @@
+build
+.dart_tool
+scripts
+Makefile
+.dockerignore
+Dockerfile

+ 1 - 1
gmus-mobile/.env.example

@@ -1,2 +1,2 @@
 DART_ENV=development
-API_URL=https://10.0.2.2:3003
+API_URL=10.0.2.2:3003

+ 33 - 0
gmus-mobile/Dockerfile

@@ -0,0 +1,33 @@
+FROM ubuntu:18.04
+
+RUN apt update && apt install -y curl git unzip xz-utils zip libglu1-mesa openjdk-8-jdk wget
+
+RUN useradd -ms /bin/bash developer
+USER developer
+WORKDIR /home/developer
+
+RUN mkdir -p Android/sdk
+ENV ANDROID_SDK_ROOT /home/developer/Android/sdk
+RUN mkdir -p .android && touch .android/repositories.cfg
+
+RUN wget -O sdk-tools.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip
+RUN unzip sdk-tools.zip && rm sdk-tools.zip
+RUN mv tools Android/sdk/tools
+RUN cd Android/sdk/tools/bin && yes | ./sdkmanager --licenses
+RUN cd Android/sdk/tools/bin && ./sdkmanager "build-tools;29.0.2" "patcher;v4" "platform-tools" "platforms;android-29" "sources;android-29"
+ENV PATH "$PATH:/home/developer/Android/sdk/platform-tools"
+
+RUN git clone https://github.com/flutter/flutter.git
+ENV PATH "$PATH:/home/developer/flutter/bin"
+
+RUN flutter precache
+
+RUN mkdir app
+WORKDIR app
+
+COPY . .
+USER root
+RUN chown -R developer:developer /home/developer/app
+USER developer
+
+RUN flutter pub get

+ 33 - 0
gmus-mobile/Makefile

@@ -0,0 +1,33 @@
+NAME 	:= docker.fela.space/gmus-mobile
+TAG 	:= $$(git log -1 --pretty=%H)
+IMG 	:= ${NAME}:${TAG}
+
+get_image:
+	@echo ${IMG}
+
+build.docker:
+	docker build -t ${IMG} .
+
+prepare.android:
+	@./scripts/prepare_android.sh
+
+build.android:
+	@flutter build appbundle --dart-define=API_URL=${GMUS_MOBILE_API_URL}
+
+bundle.android:
+	@./scripts/bundle.sh
+
+install.android:
+	@bundletool install-apks --apks=build/app/outputs/bundle/release/app-release.apks
+
+android-install:
+	make prepare.android
+	make build.android
+	make bundle.android
+	make install.android
+
+run:
+	flutter run --dart-define=API_URL="${GMUS_MOBILE_API_URL}"
+
+test.flutter:
+	flutter test

+ 3 - 14
gmus-mobile/README.md

@@ -1,16 +1,5 @@
-# gmus_mobile
+# gmus-mobile
 
-A new Flutter project.
+Mobile app written in Flutter.
 
-## Getting Started
-
-This project is a starting point for a Flutter application.
-
-A few resources to get you started if this is your first Flutter project:
-
-- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
-
-For help getting started with Flutter, view our
-[online documentation](https://flutter.dev/docs), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
+The App interacts with the [API](../gmus-backend/README.md).

+ 17 - 5
gmus-mobile/android/app/build.gradle

@@ -25,6 +25,12 @@ apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 
+def keystoreProperties = new Properties()
+def keystorePropertiesFile = rootProject.file('key.properties')
+if (keystorePropertiesFile.exists()) {
+    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+}
+
 android {
     compileSdkVersion 29
 
@@ -37,19 +43,25 @@ android {
     }
 
     defaultConfig {
-        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
-        applicationId "com.example.gmus_mobile"
+        applicationId "space.fela.gmus"
         minSdkVersion 16
         targetSdkVersion 29
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
     }
 
+    signingConfigs {
+        release {
+            keyAlias keystoreProperties['keyAlias']
+            keyPassword keystoreProperties['keyPassword']
+            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
+            storePassword keystoreProperties['storePassword']
+        }
+    }
+
     buildTypes {
         release {
-            // TODO: Add your own signing config for the release build.
-            // Signing with the debug keys for now, so `flutter run --release` works.
-            signingConfig signingConfigs.debug
+            signingConfig signingConfigs.release
         }
     }
 }

+ 3 - 2
gmus-mobile/android/app/src/main/AndroidManifest.xml

@@ -7,8 +7,8 @@
          FlutterApplication and put your custom class here. -->
     <application
         android:name="io.flutter.app.FlutterApplication"
-        android:label="gmus_mobile"
-        android:icon="@mipmap/ic_launcher">
+        android:label="gmus"
+        android:icon="@mipmap/launcher_icon">
         <activity
             android:name=".MainActivity"
             android:launchMode="singleTop"
@@ -44,4 +44,5 @@
             android:name="flutterEmbedding"
             android:value="2" />
     </application>
+    <uses-permission android:name="android.permission.INTERNET" />
 </manifest>

BIN
gmus-mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-hdpi/launcher_icon.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-mdpi/launcher_icon.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
gmus-mobile/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png


BIN
gmus-mobile/assets/icon/icon.png


+ 1 - 1
gmus-mobile/ios/Runner.xcodeproj/project.pbxproj

@@ -492,4 +492,4 @@
 /* End XCConfigurationList section */
 	};
 	rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
+}

BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png


BIN
gmus-mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png


+ 2 - 2
gmus-mobile/lib/components/albums.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:http/http.dart' as http;
 
-import '../config.dart';
+import '../utils/url.dart';
 
 import './spinner.dart';
 
@@ -22,7 +22,7 @@ class Albums extends StatefulWidget {
 }
 
 Future<List<String>> fetchAlbums(String artist) async {
-  final response = await http.get(Uri.https(config['apiUrl'], '/albums', {
+  final response = await http.get(formattedUrl('/albums', {
     'artist': artist,
   }));
 

+ 3 - 2
gmus-mobile/lib/components/artists.dart

@@ -3,7 +3,8 @@ import 'dart:convert';
 import 'package:flutter/material.dart';
 import 'package:http/http.dart' as http;
 
-import '../config.dart';
+import '../utils/url.dart';
+
 import './spinner.dart';
 
 class Artists extends StatefulWidget {
@@ -18,7 +19,7 @@ class Artists extends StatefulWidget {
 }
 
 Future<List<String>> fetchArtists() async {
-  final response = await http.get(Uri.https(config['apiUrl'], '/artists'));
+  final response = await http.get(formattedUrl('/artists'));
 
   if (response.statusCode == 200) {
     return List<String>.from(jsonDecode(response.body)['artists']);

+ 0 - 1
gmus-mobile/lib/components/content.dart

@@ -20,4 +20,3 @@ class Content extends StatelessWidget {
     });
   }
 }
-

+ 2 - 2
gmus-mobile/lib/components/songs.dart

@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:http/http.dart' as http;
 
-import '../config.dart';
 import '../types/song.dart';
+import '../utils/url.dart';
 
 import './spinner.dart';
 
@@ -29,7 +29,7 @@ class Songs extends StatefulWidget {
 }
 
 Future<List<Song>> fetchSongs(String artist) async {
-  final response = await http.get(Uri.https(config['apiUrl'], '/songs', {
+  final response = await http.get(formattedUrl('/songs', {
     'artist': artist,
   }));
 

+ 3 - 1
gmus-mobile/lib/config.dart

@@ -1,6 +1,8 @@
 import 'package:flutter_dotenv/flutter_dotenv.dart';
 
+const apiUrl = String.fromEnvironment('API_URL') ?? 'localhost:3000';
+
 final config = {
   'isDevelopment': env['DART_ENV'] == 'development',
-  'apiUrl': env['API_URL'] ?? 'http://localhost:3000',
+  'apiUrl': env['API_URL'] ?? apiUrl,
 };

+ 1 - 1
gmus-mobile/lib/main.dart

@@ -43,7 +43,7 @@ class MyHttpOverrides extends HttpOverrides {
 }
 
 Future<void> main() async {
-  await DotEnv.load(fileName: '.env');
+  await DotEnv.load();
 
   if (config['isDevelopment']) {
     HttpOverrides.global = new MyHttpOverrides();

+ 14 - 0
gmus-mobile/lib/utils/url.dart

@@ -0,0 +1,14 @@
+import '../config.dart';
+
+Uri formattedUrl(String path, [Map<String, dynamic> query]) {
+  String apiUrl = config['apiUrl'];
+
+  if (apiUrl.indexOf('/') == -1) {
+    return Uri.https(apiUrl, path);
+  }
+
+  String host = apiUrl.substring(0, apiUrl.indexOf('/'));
+  String pathPrefix = apiUrl.substring(apiUrl.indexOf('/'));
+
+  return Uri.https(host, "$pathPrefix$path", query);
+}

+ 38 - 3
gmus-mobile/pubspec.lock

@@ -15,13 +15,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.41.2"
+  archive:
+    dependency: transitive
+    description:
+      name: archive
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.13"
   args:
     dependency: transitive
     description:
       name: args
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.0"
+    version: "1.6.0"
   async:
     dependency: transitive
     description:
@@ -160,6 +167,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.1.0"
+  flutter_launcher_icons:
+    dependency: "direct dev"
+    description:
+      name: flutter_launcher_icons
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.8.1"
   flutter_test:
     dependency: "direct dev"
     description: flutter
@@ -193,6 +207,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.0.0"
+  image:
+    dependency: transitive
+    description:
+      name: image
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.19"
   logging:
     dependency: transitive
     description:
@@ -215,7 +236,7 @@ packages:
     source: hosted
     version: "1.3.0"
   mockito:
-    dependency: "direct main"
+    dependency: "direct dev"
     description:
       name: mockito
       url: "https://pub.dartlang.org"
@@ -249,6 +270,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.10.0"
+  petitparser:
+    dependency: transitive
+    description:
+      name: petitparser
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.0"
   pub_semver:
     dependency: transitive
     description:
@@ -338,12 +366,19 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.0"
+  xml:
+    dependency: transitive
+    description:
+      name: xml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.5.1"
   yaml:
     dependency: transitive
     description:
       name: yaml
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.0"
+    version: "2.2.1"
 sdks:
   dart: ">=2.12.0-0.0 <3.0.0"

+ 10 - 11
gmus-mobile/pubspec.yaml

@@ -1,4 +1,4 @@
-name: gmus_mobile
+name: gmus
 description: Mobile client for go-music-player.
 
 # The following line prevents the package from being accidentally published to
@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 1.0.0+1
+version: 0.0.1+1
 
 environment:
   sdk: ">=2.7.0 <3.0.0"
@@ -27,30 +27,24 @@ dependencies:
   flutter_dotenv:
   get:
   http:
-  mockito:
   nanoid:
   web_socket_channel:
 
 dev_dependencies:
+  flutter_launcher_icons: "^0.8.0"
   flutter_test:
     sdk: flutter
+  mockito:
 
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec
 
-# The following section is specific to Flutter.
 flutter:
-
-  # The following line ensures that the Material Icons font is
-  # included with your application, so that you can use the icons in
-  # the material Icons class.
   uses-material-design: true
 
-  # To add assets to your application, add an assets section, like this:
   assets:
+    - assets/icon/icon.png
     - .env
-  #   - images/a_dot_burr.jpeg
-  #   - images/a_dot_ham.jpeg
 
   # An image asset can refer to one or more resolution-specific "variants", see
   # https://flutter.dev/assets-and-images/#resolution-aware.
@@ -77,3 +71,8 @@ flutter:
   #
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
+
+flutter_icons:
+  android: "launcher_icon"
+  ios: true
+  image_path: "assets/icon/icon.png"

+ 14 - 0
gmus-mobile/scripts/bundle.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -e
+
+bundletool build-apks \
+  --bundle=build/app/outputs/bundle/release/app-release.aab \
+  --output=build/app/outputs/bundle/release/app-release.apks \
+  --ks=${GMUS_ANDROID_KEYSTORE_FILE} \
+  --ks-pass=pass:${GMUS_ANDROID_KEYSTORE_PASSWORD} \
+  --ks-key-alias=key \
+  --key-pass=pass:${GMUS_ANDROID_KEYSTORE_PASSWORD} \
+  --overwrite
+
+exit 0

+ 24 - 0
gmus-mobile/scripts/prepare_android.sh

@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -e
+
+cd "$(dirname $0)"
+
+if [[ -z "$GMUS_ANDROID_KEYSTORE_FILE" ]]; then
+  echo "Must set GMUS_ANDROID_KEYSTORE_FILE"
+  exit 1
+fi
+
+if [[ -z "$GMUS_ANDROID_KEYSTORE_PASSWORD" ]]; then
+  echo "Must set GMUS_ANDROID_KEYSTORE_PASSWORD"
+  exit 1
+fi
+
+cat > ../android/key.properties << EOF
+storePassword=$GMUS_ANDROID_KEYSTORE_PASSWORD
+keyPassword=$GMUS_ANDROID_KEYSTORE_PASSWORD
+keyAlias=key
+storeFile=$GMUS_ANDROID_KEYSTORE_FILE
+EOF
+
+exit 0

+ 2 - 2
gmus-mobile/test/components/status_test.dart

@@ -2,8 +2,8 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:get/get.dart';
 
-import 'package:gmus_mobile/components/status.dart';
-import 'package:gmus_mobile/controller.dart';
+import 'package:gmus/components/status.dart';
+import 'package:gmus/controller.dart';
 
 class TestStatusBar extends StatelessWidget {
   final Controller controller;

+ 2 - 2
gmus-mobile/test/controller_test.dart

@@ -1,6 +1,6 @@
 import 'package:flutter_test/flutter_test.dart';
-import 'package:gmus_mobile/actions.dart';
-import 'package:gmus_mobile/controller.dart';
+import 'package:gmus/actions.dart';
+import 'package:gmus/controller.dart';
 import 'package:mockito/mockito.dart';
 import 'package:web_socket_channel/io.dart';
 import 'package:web_socket_channel/web_socket_channel.dart';

+ 5 - 0
k8s/deploy.sh

@@ -7,6 +7,11 @@ cd $(dirname "$0")
 IMAGE_BACKEND=$(make -f ../gmus-backend/Makefile get_image)
 IMAGE_WEB=$(make -f ../gmus-web/Makefile get_image)
 
+if [[ -z $LIBRARY_DIRECTORY ]]; then
+  echo "Must set LIBRARY_DIRECTORY!"
+  exit 1
+fi
+
 namespace="gmus"
 
 cat ./manifest.yml \