diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..723e4a7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,53 @@ +name: Flutter Build + +on: + push: + branches: + - main + pull_request: + +jobs: + build-app: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: '12.x' + + - name: Setup Flutter + uses: subosito/flutter-action@v1 + with: + channel: stable + + - name: Cache pub dependencies + uses: actions/cache@v2 + with: + path: ${{ env.FLUTTER_HOME }}/.pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} + restore-keys: ${{ runner.os }}-pub- + + - name: Download pub dependencies + run: flutter pub get + + - name: Download Android keystore + id: android_keystore + uses: timheuer/base64-to-file@v1.0.3 + with: + fileName: key.jks + encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + + - name: Create key.properties + run: | + echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties + echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties + echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties + echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties + + - name: Build Debug Build + run: flutter build apk --debug + diff --git a/.github/workflows/flutter-release.yml b/.github/workflows/flutter-release.yml new file mode 100644 index 0000000..b5d0afb --- /dev/null +++ b/.github/workflows/flutter-release.yml @@ -0,0 +1,70 @@ +# Based on https://medium.com/flutter-community/automating-publishing-your-flutter-apps-to-google-play-using-github-actions-2f67ac582032 + +name: Flutter release + +on: + push: + branches: + - main + release: + types: [published] + +jobs: + release: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: '12.x' + + - name: Setup Flutter + uses: subosito/flutter-action@v1 + with: + channel: stable + + - name: Flutter version + run: flutter --version + + - name: Cache pub dependencies + uses: actions/cache@v2 + with: + path: ${{ env.FLUTTER_HOME }}/.pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} + restore-keys: ${{ runner.os }}-pub- + + - name: Download pub dependencies + run: flutter pub get + + - name: Download Android keystore + id: android_keystore + uses: timheuer/base64-to-file@v1.0.3 + with: + fileName: key.jks + encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + + - name: Create key.properties + run: | + echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties + echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties + echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties + echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties + + - name: Build Android App Bundle + run: flutter build appbundle + + - name: Build Android APK + run: flutter build apk + + - name: Upload build artifacts + uses: actions/upload-artifact@v2 + with: + name: app-release-bundle + path: | + build/app/outputs/bundle/release/app-release.aab + build/app/outputs/flutter-apk/app-release.apk + diff --git a/.gitignore b/.gitignore index ab3e5ac..65812a0 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,24 @@ ios/fastlane/README.md ios/fastlane/report.xml ios/Runner.ipa ios/Runner.app.dSYM.zip +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# VS Code +.vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1b2ba13 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "configurations": [ + { + "name": "Flutter", + "request": "launch", + "type": "dart", + "flutterMode": "debug" + } + ] +} diff --git a/README.md b/README.md index 1d5bc28..ae40de6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# Vikunja Cross-Platform app -## Vikunja as Flutter cross-platform app. -I have started maintaining the flutter app. It is currently in a very early stage, but already somehow usable. If you have feature requests or issues, please let me know on the issues tab. -Development for IOS has been put on hold as I do not have the resources to develop for IOS. +# Vikunja Cross-Plattform app + +[![Build Status](https://drone.kolaente.de/api/badges/vikunja/app/status.svg)](https://drone.kolaente.de/vikunja/app) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Download](https://img.shields.io/badge/download-v0.1-brightgreen.svg)](https://storage.kolaente.de/minio/vikunja-app/) +[![TestFlight Beta](https://img.shields.io/badge/TestFlight-Beta-026CBB)](https://testflight.apple.com/join/KxOaAraq) + +Vikunja as Flutter cross platform app. + +## TODO +- Move all api responses to Response type +- Save loaded tasks for a while to optimize data usage +- \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore index 65b7315..d0b98b2 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -8,3 +8,12 @@ /build /captures GeneratedPluginRegistrant.java +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/android/app/build.gradle b/android/app/build.gradle index 9103825..5b8c7ea 100644 --- a/android/app/build.gradle +++ b/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 31 @@ -40,11 +46,23 @@ android { applicationId "io.vikunja.app" minSdkVersion 19 targetSdkVersion 31 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } + /* + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + */ + flavorDimensions "deploy" productFlavors { @@ -74,7 +92,10 @@ android { 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 + } + debug { + //signingConfig signingConfigs.debug } } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ba10664..18aebb3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -26,6 +26,23 @@ android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:exported="true"> + + + + diff --git a/android/app/src/main/kotlin/io/vikunja/flutteringvikunja/MainActivity.kt b/android/app/src/main/kotlin/io/vikunja/flutteringvikunja/MainActivity.kt index 805f665..3d4adbc 100644 --- a/android/app/src/main/kotlin/io/vikunja/flutteringvikunja/MainActivity.kt +++ b/android/app/src/main/kotlin/io/vikunja/flutteringvikunja/MainActivity.kt @@ -2,4 +2,5 @@ package io.vikunja.flutteringvikunja import io.flutter.embedding.android.FlutterActivity -class MainActivity : FlutterActivity() +class MainActivity : FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 00fa441..c853cb5 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ + + + diff --git a/android/gradlew b/android/gradlew old mode 100755 new mode 100644 diff --git a/lib/api/client.dart b/lib/api/client.dart index 1169012..6cb543c 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -1,6 +1,9 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:core'; import 'package:http/http.dart' as http; +import 'package:vikunja_app/api/response.dart'; +import 'package:vikunja_app/components/string_extension.dart'; class Client { final JsonDecoder _decoder = new JsonDecoder(); @@ -25,34 +28,54 @@ class Client { 'Content-Type': 'application/json' }; - // TODO: use Uri properly - Future get(String url) { + Future get(String url, + [Map> queryParameters]) { + // TODO: This could be moved to a seperate function + var uri = Uri.parse('${this.base}$url'); + // Because these are all final values, we can't just add the queryParameters and must instead build a new Uri Object every time this method is called. + var newUri = Uri( + scheme: uri.scheme, + userInfo: uri.userInfo, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + queryParameters: queryParameters, + // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. + fragment: uri.fragment); + return http.get(newUri, headers: _headers).then(_handleResponse); + } + + Future delete(String url) { return http - .get(Uri.parse('${this.base}$url'), headers: _headers) + .delete( + '${this.base}$url'.toUri(), + headers: _headers, + ) .then(_handleResponse); } - Future delete(String url) { + Future post(String url, {dynamic body}) { return http - .delete(Uri.parse('${this.base}$url'), headers: _headers) + .post( + '${this.base}$url'.toUri(), + headers: _headers, + body: _encoder.convert(body), + ) .then(_handleResponse); } - Future post(String url, {dynamic body}) { + Future put(String url, {dynamic body}) { return http - .post(Uri.parse('${this.base}$url'), - headers: _headers, body: _encoder.convert(body)) + .put( + '${this.base}$url'.toUri(), + headers: _headers, + body: _encoder.convert(body), + ) .then(_handleResponse); } - Future put(String url, {dynamic body}) { - return http - .put(Uri.parse('${this.base}$url'), - headers: _headers, body: _encoder.convert(body)) - .then(_handleResponse); - } - - dynamic _handleResponse(http.Response response) { + Response _handleResponse(http.Response response) { if (response.statusCode < 200 || response.statusCode >= 400 || json == null) { @@ -66,12 +89,14 @@ class Client { throw new ApiException( response.statusCode, response.request.url.toString()); } - return _decoder.convert(response.body); + return Response( + _decoder.convert(response.body), response.statusCode, response.headers); } } class InvalidRequestApiException extends ApiException { final String message; + InvalidRequestApiException(int errorCode, String path, this.message) : super(errorCode, path); @@ -84,6 +109,7 @@ class InvalidRequestApiException extends ApiException { class ApiException implements Exception { final int errorCode; final String path; + ApiException(this.errorCode, this.path); @override diff --git a/lib/api/label_task.dart b/lib/api/label_task.dart new file mode 100644 index 0000000..c9b1b77 --- /dev/null +++ b/lib/api/label_task.dart @@ -0,0 +1,32 @@ +import 'package:vikunja_app/api/client.dart'; +import 'package:vikunja_app/api/service.dart'; +import 'package:vikunja_app/models/label.dart'; +import 'package:vikunja_app/models/labelTask.dart'; +import 'package:vikunja_app/service/services.dart'; + +class LabelTaskAPIService extends APIService implements LabelTaskService { + LabelTaskAPIService(Client client) : super(client); + + @override + Future