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