Compare commits

..

25 Commits

Author SHA1 Message Date
Jonas Franz 9fbdd2d709
Add download ios artifacts 2018-10-08 17:57:43 +02:00
Jonas Franz f2b7bb0bc3
Fix dockerfile 2018-10-08 17:08:12 +02:00
Jonas Franz ca7bf35f7b
Add scp docker image 2018-10-08 16:58:55 +02:00
Jonas Franz fcf0d9ecf9
Merge branch 'master' of ssh://git.kolaente.de:9022/vikunja/app into feature/drone-ios 2018-10-08 16:31:08 +02:00
Jonas Franz e617d93ee0
Fix xcode project 2018-09-24 10:55:59 +02:00
Jonas Franz dad730ed1c
Cleanup 2018-09-23 22:59:03 +02:00
Jonas Franz b04307013f
Start bin bash 2018-09-23 22:47:56 +02:00
Jonas Franz 21fdec0aa8
Add /bin/bash 2018-09-23 22:45:36 +02:00
Jonas Franz b77ce08243
Revert "Start bin bash"
This reverts commit d87fcd5
2018-09-23 22:45:13 +02:00
Jonas Franz d87fcd57d1
Start bin bash 2018-09-23 22:44:51 +02:00
Jonas Franz 9481dd09e4
Add Podfile 2018-09-23 22:41:45 +02:00
Jonas Franz 7e1e8f2d61
Add ls lah 2018-09-23 22:35:07 +02:00
Jonas Franz 554f23a981
Use correct filename 2018-09-23 22:32:39 +02:00
Jonas Franz d0cda640c3
Outputting catfile 2018-09-23 22:32:22 +02:00
Jonas Franz 2483c1b2eb
Use correct path for remote build 2018-09-23 22:30:30 +02:00
Jonas Franz 79fb00afb7
Add echo 2018-09-23 22:29:18 +02:00
Jonas Franz 73edc6583c
Install cocoapods 2018-09-23 22:26:07 +02:00
Jonas Franz 7b37a7c400
Add longer timeout to SSH build 2018-09-23 22:22:14 +02:00
Jonas Franz cfe9befdfa
Move export statement to correct position 2018-09-23 22:19:14 +02:00
Jonas Franz 5dda43a97b
Change macos port 2018-09-23 22:17:07 +02:00
Jonas Franz f0a9cd52c9
Fix drone 2018-09-23 21:32:12 +02:00
Jonas Franz e830e24d36
Add flutter path 2018-09-23 16:52:34 +02:00
Jonas Franz fa5d3eba44
Fix make file
Add clean ios step
2018-09-23 16:45:25 +02:00
Jonas Franz 3c53045598
Add build_ios 2018-09-23 16:40:29 +02:00
Jonas Franz 1add0c89c1
Upload code to vm 2018-09-23 16:18:06 +02:00
63 changed files with 1029 additions and 1927 deletions

View File

@ -1,189 +1,105 @@
kind: pipeline
type: docker
name: testing
workspace:
base: /home/cirrus/app
base: /app
clone:
depth: 50
git:
image: plugins/git
depth: 50
tags: true
# Only run on prs or pushes to master
trigger:
branch:
include:
- master
event:
include:
- push
- pull_request
pipeline:
upload_to_macos:
image: appleboy/drone-scp
host: home.jonasfranz.software
secrets: [ ssh_username, ssh_key ]
port: 2222
source: ./
target: /tmp/drone_build_${DRONE_BUILD_NUMBER}
when:
event: [ push, tag]
steps:
- name: build
image: cirrusci/flutter:stable
pull: true
test:
image: nathansamson/flutter-builder-docker:v0.6.0
pull: true
commands:
- make test
when:
event: [ push, tag, pull_request ]
build:
image: nathansamson/flutter-builder-docker:v0.6.0
group: build
pull: true
commands:
- flutter packages get
- make build-all
- mkdir apks
- mv build/app/outputs/apk/*/*/*.apk apks
when:
event: [ push, tag ]
build_ios:
image: appleboy/drone-ssh
group: build
host: home.jonasfranz.software
port: 2222
secrets: [ ssh_username, ssh_key ]
command_timeout: 900
script:
- source ~/.bash_profile
- export EXPANDED_CODE_SIGN_IDENTITY=""
- cd /tmp/drone_build_${DRONE_BUILD_NUMBER}/ios
- pod install
- cd ..
- flutter packages get
- make build-ios-all
when:
event: [ push, tag ]
download_ios_artifacts:
image: vikunja/scp
secrets: [ ssh_username, ssh_key ]
commands:
- flutter packages get
- make format-check
- make build-debug
# Don't run tests until we have to not break the pipeline
# - name: test
# image: cirrusci/flutter:stable
# pull: true
# commands:
# - flutter packages get
# - make test
---
kind: pipeline
type: docker
name: release-latest
depends_on:
- testing
trigger:
branch:
- master
event:
- push
workspace:
base: /home/cirrus/app
clone:
depth: 50
steps:
# Because drone separates the pipelines, we have to add the build step to this pipeline. This is double code, we should change it at some point if possible.
- name: build
image: cirrusci/flutter:stable
pull: true
commands:
- flutter packages get
- make build-all
- mkdir apks
- mv build/app/outputs/apk/*/*/*.apk apks
# Push the releases to our pseudo-s3-bucket
- name: release
- echo $ssh_key | scp -P 2222 -i /dev/stdin ${ssh_username}@home.jonasfranz.software:/tmp/drone_build_${DRONE_BUILD_NUMBER}/build/ios/iphoneos/* apks/
when:
event: [ push, tag ]
# Push the releases to our pseudo-s3-bucket
release:
image: plugins/s3:1
pull: true
settings:
bucket: vikunja-releases
access_key:
from_secret: aws_access_key_id
secret_key:
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: apks/
source: apks/*
target: /app/master
secrets: [ aws_access_key_id, aws_secret_access_key ]
bucket: vikunja-app
endpoint: https://storage.kolaente.de
path_style: true
strip_prefix: apks/
source: apks/*
target: /${DRONE_TAG##v}
when:
event: [ tag ]
---
kind: pipeline
type: docker
name: release-version
depends_on:
- testing
trigger:
event:
- tag
workspace:
base: /home/cirrus/app
clone:
depth: 50
steps:
# Because drone separates the pipelines, we have to add the build step to this pipeline. This is double code, we should change it at some point if possible.
- name: build
image: cirrusci/flutter:stable
pull: true
commands:
- flutter packages get
- make build-all
- mkdir apks
- mv build/app/outputs/apk/*/*/*.apk apks
# Push the releases to our pseudo-s3-bucket
- name: release
# Push the releases to our pseudo-s3-bucket
release:
image: plugins/s3:1
pull: true
settings:
bucket: vikunja-releases
access_key:
from_secret: aws_access_key_id
secret_key:
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: apks/
source: apks/*
target: /app/${DRONE_TAG##v}
secrets: [ aws_access_key_id, aws_secret_access_key ]
bucket: vikunja-app
endpoint: https://storage.kolaente.de
path_style: true
strip_prefix: apks/
source: apks/*
target: /master
when:
event: [ push ]
branch: [ master ]
---
kind: pipeline
type: exec
name: release-ios
trigger:
event:
- push
branch:
- master
platform:
os: darwin
arch: amd64
steps:
- name: build
commands:
- make build-ios
environment:
HOME: /Users/buildslave
- name: deploy
environment:
HOME: /Users/buildslave
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
FASTLANE_SKIP_UPDATE_CHECK: true
FASTLANE_HIDE_CHANGELOG: true
MATCH_PASSWORD:
from_secret: match_password
FASTLANE_PASSWORD:
from_secret: fastlane_password
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD:
from_secret: fastlane_apple_password
FASTLANE_SESSION:
from_secret: fastlane_session
KEYCHAIN_PASSWORD:
from_secret: keychain_password
CONTACT_EMAIL:
from_secret: contact_email
CONTACT_FIRST_NAME:
from_secret: contact_first_name
CONTACT_LAST_NAME:
from_secret: contact_last_name
CONTACT_PHONE:
from_secret: contact_phone
commands:
- eval "$(rbenv init -)"
- rbenv shell 2.5.0
- cd ios
- bundle config set --local path '.vendor/bundle'
- bundle install
- bundle exec fastlane ios signing
- bundle exec fastlane ios beta
depends_on:
- testing
clean_ios:
image: appleboy/drone-ssh
host: home.jonasfranz.software
port: 2222
secrets: [ ssh_username, ssh_key ]
script:
- rm -rf /tmp/drone_build_${DRONE_BUILD_NUMBER}
when:
status: [ failure, success ]
event: [ push, tag]

8
.gitignore vendored
View File

@ -27,8 +27,6 @@
.pub-cache/
.pub/
build/
!pubspec.lock
.flutter-plugins-dependencies
# Android related
**/android/**/gradle-wrapper.jar
@ -70,8 +68,4 @@ build/
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
ios/fastlane/README.md
ios/fastlane/report.xml
ios/Runner.ipa
ios/Runner.app.dSYM.zip
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

Binary file not shown.

View File

@ -0,0 +1,2 @@
#Sat Sep 15 16:48:22 CEST 2018
gradle.version=4.4

3
Dockerfile.scp Normal file
View File

@ -0,0 +1,3 @@
FROM alpine
RUN apk add --no-cache openssh-client
RUN mkdir ~/.ssh && ssh-keyscan -H home.jonasfranz.software -P 2222 >> ~/.ssh/known_hosts

View File

@ -1,45 +1,47 @@
GIT_LAST_COMMIT := $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
FLUTTER ?= flutter
ifneq ($(DRONE_BUILD_NUMBER),)
VERSION ?= $(DRONE_BUILD_NUMBER)
ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG))-$(GIT_LAST_COMMIT)
else
VERSION ?= 1
ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))-$(GIT_LAST_COMMIT)
else
VERSION ?= master-$(GIT_LAST_COMMIT)
endif
endif
.PHONY: test
test:
$(FLUTTER) test
flutter test
.PHONY: build-all
build-all: build-release build-debug build-profile
.PHONY: build-release
build-release:
$(FLUTTER) build apk --release --build-number=$(VERSION) --flavor main
flutter build apk --release --build-name=$(VERSION) --flavor main
.PHONY: build-debug
build-debug:
$(FLUTTER) build apk --debug --build-number=$(VERSION) --flavor unsigned
flutter build apk --debug --build-name=$(VERSION) --flavor unsigned
.PHONY: build-profile
build-profile:
$(FLUTTER) build apk --profile --build-number=$(VERSION) --flavor unsigned
flutter build apk --profile --build-name=$(VERSION) --flavor unsigned
.PHONY: build-ios
build-ios:
$(FLUTTER) build ios --release --build-number=$(VERSION) --no-codesign
.PHONY: build-ios-all
build-ios-all: build-ios-release build-ios-debug build-ios-profile
.PHONY: format
format:
$(FLUTTER) format lib
.PHONY: build-ios-release
build-ios-release:
flutter build ios --release --build-name=$(VERSION) --no-codesign
.PHONY: format-check
format-check:
@diff=$$(flutter format -n lib); \
if [ -n "$$diff" ]; then \
echo "The following files are not formatted correctly:"; \
echo "$${diff}"; \
echo "Please run 'make format' and commit the result."; \
exit 1; \
fi;
.PHONY: build-ios-debug
build-ios-debug:
flutter build ios --debug --build-name=$(VERSION) --no-codesign
mv build/ios/iphoneos/Runner.app build/ios/iphoneos/Runner-debug.app
.PHONY: build-ios-profile
build-ios-profile:
flutter build ios --profile --build-name=$(VERSION) --no-codesign
mv build/ios/iphoneos/Runner.app build/ios/iphoneos/Runner-profile.app

View File

@ -1,8 +1,7 @@
# Vikunja Cross-Platform app
# 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.
Vikunja as Flutter cross platform app.

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
compileSdkVersion 27
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@ -37,12 +37,14 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.vikunja.app"
minSdkVersion 19
targetSdkVersion 28
minSdkVersion 18
targetSdkVersion 27
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
flavorDimensions "deploy"

View File

@ -13,6 +13,7 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="Vikunja"
android:icon="@mipmap/ic_launcher">
<activity
@ -22,13 +23,17 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</activity>
</application>
</manifest>

View File

@ -1,5 +1,13 @@
package io.vikunja.flutteringvikunja
import io.flutter.embedding.android.FlutterActivity
import android.os.Bundle
class MainActivity : FlutterActivity()
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity(): FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}

View File

@ -21,6 +21,8 @@ allprojects {
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

View File

@ -1,4 +1 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip

View File

@ -1 +0,0 @@
include ':app'

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,18 +0,0 @@
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
end

View File

@ -1,3 +0,0 @@
source "https://rubygems.org"
gem "fastlane"

View File

@ -4,38 +4,62 @@
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
pods_ary = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
# Flutter Pods
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join('.symlinks', 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
end
}
# Plugin Pods
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p|
symlink = File.join('.symlinks', 'plugins', p[:name])
File.symlink(p[:path], symlink)
pod p[:name], :path => File.join(symlink, 'ios')
}
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

View File

@ -8,8 +8,13 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@ -24,6 +29,8 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -33,21 +40,21 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1CD140BF817CCDA821C9152F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
BD0C880424A0CB4300291E83 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
C102A622A93B95B5704BDD24 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CA78C9A9831542FDDB6FB31E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -55,6 +62,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
ACA854A11123D371B9168194 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -73,8 +82,6 @@
7CACC4C503C5D851EB73C215 /* Pods */ = {
isa = PBXGroup;
children = (
CA78C9A9831542FDDB6FB31E /* Pods-Runner.debug.xcconfig */,
1CD140BF817CCDA821C9152F /* Pods-Runner.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@ -82,7 +89,10 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
@ -112,7 +122,6 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
BD0C880424A0CB4300291E83 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@ -148,6 +157,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
85082CB7F9EE2F3E7985BDB9 /* [CP] Embed Pods Frameworks */,
7C22F5DE30AEBAB42040EB3F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -171,7 +181,6 @@
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = Z48VLBM2R7;
LastSwiftMigration = 0910;
ProvisioningStyle = Manual;
};
};
};
@ -180,7 +189,6 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
@ -203,6 +211,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -240,7 +249,22 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
7C22F5DE30AEBAB42040EB3F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
85082CB7F9EE2F3E7985BDB9 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
@ -248,8 +272,8 @@
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework",
);
name = "[CP] Embed Pods Frameworks";
@ -259,7 +283,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
@ -312,6 +336,7 @@
/* Begin XCBuildConfiguration section */
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -365,6 +390,7 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -417,8 +443,6 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = Z48VLBM2R7;
ENABLE_BITCODE = NO;
@ -434,7 +458,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = io.vikunja.flutteringVikunja;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match Development io.vikunja.flutteringVikunja 1592303885";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = On;
@ -449,9 +472,6 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = Z48VLBM2R7;
ENABLE_BITCODE = NO;
@ -467,7 +487,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = io.vikunja.flutteringVikunja;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.vikunja.flutteringVikunja 1592303767";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = On;
SWIFT_VERSION = 4.0;

View File

@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>

View File

@ -1,8 +0,0 @@
app_identifier("io.vikunja.flutteringVikunja") # The bundle identifier of your app
apple_id("email@jfdev.de") # Your Apple email address
itc_team_id("117734679") # App Store Connect Team ID
team_id("Z48VLBM2R7") # Developer Portal Team ID
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile

View File

@ -1,45 +0,0 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
match(type: "appstore", readonly: true)
gym
upload_to_testflight(
beta_app_feedback_email: "hello@vikunja.io",
beta_app_description: "Automated Vikunja App Build",
demo_account_required: true,
distribute_external: true,
groups: ["PublicBeta"],
changelog: "Automated Vikunja Build",
beta_app_review_info: {
contact_email: ENV["CONTACT_EMAIL"],
contact_first_name: ENV["CONTACT_FIRST_NAME"],
contact_last_name: ENV["CONTACT_LAST_NAME"],
contact_phone: ENV["CONTACT_PHONE"],
demo_account_name: "demo",
demo_account_password: "demo",
notes: "Please use https://try.vikunja.io as URL"
}
)
end
lane :signing do
match(type: "appstore", readonly: true)
match(type: "development", readonly: true)
end
end

View File

@ -1,13 +0,0 @@
git_url("git@git.jfdev.de:JonasFranzDEV/match.git")
storage_mode("git")
type("development") # The default type, can be: appstore, adhoc, enterprise or development
# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
# username("user@fastlane.tools") # Your Apple Developer Portal username
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match

View File

@ -53,15 +53,8 @@ class Client {
dynamic _handleResponse(http.Response response) {
if (response.statusCode < 200 ||
response.statusCode >= 400 ||
response.statusCode > 400 ||
json == null) {
if (response.statusCode ~/ 100 == 4) {
Map<String, dynamic> error = _decoder.convert(response.body);
throw new InvalidRequestApiException(
response.statusCode,
response.request.url.toString(),
error["message"] ?? "Unknown Error");
}
throw new ApiException(
response.statusCode, response.request.url.toString());
}
@ -69,17 +62,6 @@ class Client {
}
}
class InvalidRequestApiException extends ApiException {
final String message;
InvalidRequestApiException(int errorCode, String path, this.message)
: super(errorCode, path);
@override
String toString() {
return this.message;
}
}
class ApiException implements Exception {
final int errorCode;
final String path;

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:vikunja_app/api/client.dart';
import 'package:vikunja_app/api/service.dart';
import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/service/services.dart';
class ListAPIService extends APIService implements ListService {
@ -40,7 +40,7 @@ class ListAPIService extends APIService implements ListService {
@override
Future<TaskList> update(TaskList tl) {
return client
.post('/lists/${tl.id}', body: tl.toJSON())
.put('/lists/${tl.id}', body: tl.toJSON())
.then((map) => TaskList.fromJson(map));
}
}

View File

@ -1,20 +0,0 @@
import 'package:flutter/material.dart';
class ErrorDialog extends StatelessWidget {
final dynamic error;
ErrorDialog({this.error});
@override
Widget build(BuildContext context) {
return AlertDialog(
content: Text(error.toString()),
actions: <Widget>[
FlatButton(
child: Text('Close'),
onPressed: () => Navigator.of(context).maybePop(),
)
],
);
}
}

View File

@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
import 'package:crypto/crypto.dart';
class GravatarImageProvider extends NetworkImage {
GravatarImageProvider(String email)
: super("https://secure.gravatar.com/avatar/" +
md5.convert(email.trim().toLowerCase().codeUnits).toString());
}

View File

@ -41,20 +41,16 @@ class TaskTileState extends State<TaskTile> {
strokeWidth: 2.0,
)),
),
title: Text(_currentTask.title),
title: Text(_currentTask.text),
subtitle:
_currentTask.description == null || _currentTask.description.isEmpty
? null
: Text(_currentTask.description),
trailing: IconButton(
icon: Icon(Icons.settings),
onPressed: () {
null; // TODO: implement edit task
}),
trailing: IconButton(icon: Icon(Icons.settings), onPressed: null),
);
}
return CheckboxListTile(
title: Text(_currentTask.title),
title: Text(_currentTask.text),
controlAffinity: ListTileControlAffinity.leading,
value: _currentTask.done ?? false,
subtitle:
@ -83,7 +79,7 @@ class TaskTileState extends State<TaskTile> {
return VikunjaGlobal.of(context).taskService.update(Task(
id: task.id,
done: checked,
title: task.title,
text: task.text,
description: task.description,
owner: null,
));

View File

@ -0,0 +1,106 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/pages/list_page.dart';
class NamespaceFragment extends StatefulWidget {
final Namespace namespace;
NamespaceFragment({this.namespace})
: super(key: Key(namespace.id.toString()));
@override
_NamespaceFragmentState createState() => new _NamespaceFragmentState();
}
class _NamespaceFragmentState extends State<NamespaceFragment> {
List<TaskList> _lists = [];
bool _loading = true;
@override
Widget build(BuildContext context) {
return Scaffold(
body: !this._loading
? RefreshIndicator(
child: new ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children: ListTile.divideTiles(
context: context,
tiles: _lists.map((ls) => Dismissible(
key: Key(ls.id.toString()),
direction: DismissDirection.startToEnd,
child: ListTile(
title: new Text(ls.title),
onTap: () => _openList(context, ls),
trailing: Icon(Icons.arrow_right),
),
background: Container(
color: Colors.red,
child: const ListTile(
leading: Icon(Icons.delete,
color: Colors.white, size: 36.0)),
),
onDismissed: (direction) {
_removeList(ls).then((_) => Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text("${ls.title} removed"))));
},
))).toList(),
),
onRefresh: _updateLists,
)
: Center(child: CircularProgressIndicator()),
floatingActionButton: FloatingActionButton(
onPressed: () => _addListDialog(), child: const Icon(Icons.add)),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_updateLists();
}
Future _removeList(TaskList list) {
return VikunjaGlobal.of(context)
.listService
.delete(list.id)
.then((_) => _updateLists());
}
Future<void> _updateLists() {
return VikunjaGlobal.of(context)
.listService
.getByNamespace(widget.namespace.id)
.then((lists) => setState(() {
this._lists = lists;
this._loading = false;
}));
}
_openList(BuildContext context, TaskList list) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ListPage(taskList: list)));
}
_addListDialog() {
showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: _addList,
decoration: new InputDecoration(
labelText: 'List Name', hintText: 'eg. Shopping List')),
);
}
_addList(String name) {
VikunjaGlobal.of(context)
.listService
.create(widget.namespace.id, TaskList(id: null, title: name, tasks: []))
.then((_) => setState(() {}));
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
class PlaceholderPage extends StatelessWidget {
class PlaceholderFragment extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Container(
@ -15,7 +15,7 @@ class PlaceholderPage extends StatelessWidget {
style: Theme.of(context).textTheme.headline,
),
),
new Text('Please select a namespace by tapping the ☰ icon.',
new Text('Please select a namespace by clicking the ☰ icon.',
style: Theme.of(context).textTheme.subhead),
],
));

View File

@ -7,6 +7,7 @@ import 'package:vikunja_app/api/task_implementation.dart';
import 'package:vikunja_app/api/user_implementation.dart';
import 'package:vikunja_app/managers/user.dart';
import 'package:vikunja_app/models/user.dart';
import 'package:vikunja_app/service/mocked_services.dart';
import 'package:vikunja_app/service/services.dart';
class VikunjaGlobal extends StatefulWidget {
@ -33,17 +34,12 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
bool _loading = true;
User get currentUser => _currentUser;
Client get client => _client;
UserManager get userManager => new UserManager(_storage);
UserService newUserService(base) => new UserAPIService(Client(null, base));
NamespaceService get namespaceService => new NamespaceAPIService(client);
TaskService get taskService => new TaskAPIService(client);
ListService get listService => new ListAPIService(client);
@override
@ -77,20 +73,6 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
});
}
void logoutUser(BuildContext context) {
_storage.deleteAll().then((_) {
Navigator.pop(context);
setState(() {
_client = null;
_currentUser = null;
});
}).catchError((err) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('An error occured while logging out!'),
));
});
}
void _loadCurrentUser() async {
var currentUser = await _storage.read(key: 'currentUser');
if (currentUser == null) {

View File

@ -1,8 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/pages/home.dart';
import 'package:vikunja_app/pages/user/login.dart';
import 'package:vikunja_app/theme/theme.dart';
import 'package:vikunja_app/pages/home_page.dart';
import 'package:vikunja_app/pages/login_page.dart';
import 'package:vikunja_app/style.dart';
void main() => runApp(VikunjaGlobal(
child: new VikunjaApp(home: HomePage()),
@ -12,12 +14,12 @@ class VikunjaApp extends StatelessWidget {
final Widget home;
const VikunjaApp({Key key, this.home}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Vikunja',
theme: buildVikunjaTheme(),
darkTheme: buildVikunjaDarkTheme(),
home: this.home,
);
}

View File

@ -1,42 +0,0 @@
import 'package:meta/meta.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/models/user.dart';
class TaskList {
final int id;
final String title, description;
final User owner;
final DateTime created, updated;
final List<Task> tasks;
TaskList(
{@required this.id,
@required this.title,
this.description,
this.owner,
this.created,
this.updated,
this.tasks});
TaskList.fromJson(Map<String, dynamic> json)
: id = json['id'],
owner = User.fromJson(json['owner']),
description = json['description'],
title = json['title'],
updated = DateTime.parse(json['updated']),
created = DateTime.parse(json['created']),
tasks = (json['tasks'] == null ? [] : json['tasks'] as List<dynamic>)
?.map((taskJson) => Task.fromJson(taskJson))
?.toList();
toJSON() {
return {
"id": this.id,
"title": this.title,
"description": this.description,
"owner": this.owner?.toJSON(),
"created": this.created?.toIso8601String(),
"updated": this.updated?.toIso8601String(),
};
}
}

View File

@ -4,29 +4,29 @@ import 'package:meta/meta.dart';
class Namespace {
final int id;
final DateTime created, updated;
final String title, description;
final String name, description;
final User owner;
Namespace(
{@required this.id,
this.created,
this.updated,
@required this.title,
@required this.name,
this.description,
this.owner});
Namespace.fromJson(Map<String, dynamic> json)
: title = json['title'],
: name = json['name'],
description = json['description'],
id = json['id'],
created = DateTime.parse(json['created']),
updated = DateTime.parse(json['updated']),
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
owner = User.fromJson(json['owner']);
toJSON() => {
"created": created?.toIso8601String(),
"updated": updated?.toIso8601String(),
"title": title,
"created": created?.millisecondsSinceEpoch,
"updated": updated?.millisecondsSinceEpoch,
"name": name,
"owner": owner?.toJSON(),
"description": description
};

View File

@ -3,9 +3,8 @@ import 'package:meta/meta.dart';
class Task {
final int id;
final DateTime created, updated, due;
final List<DateTime> reminders;
final String title, description;
final DateTime created, updated, reminder, due;
final String text, description;
final bool done;
final User owner;
@ -13,37 +12,71 @@ class Task {
{@required this.id,
this.created,
this.updated,
this.reminders,
this.reminder,
this.due,
@required this.title,
@required this.text,
this.description,
@required this.done,
@required this.owner});
Task.fromJson(Map<String, dynamic> json)
: id = json['id'],
updated = DateTime.parse(json['updated']),
created = DateTime.parse(json['created']),
reminders = (json['reminder_dates'] as List<dynamic>)
?.map((r) => DateTime.parse(r))
?.toList(),
due =
json['due_date'] != null ? DateTime.parse(json['due_date']) : null,
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
reminder = DateTime.fromMillisecondsSinceEpoch(json['reminderDate']),
due = DateTime.fromMillisecondsSinceEpoch(json['dueDate']),
description = json['description'],
title = json['title'],
text = json['text'],
done = json['done'],
owner = User.fromJson(json['created_by']);
owner = User.fromJson(json['createdBy']);
toJSON() => {
'id': id,
'updated': updated?.toIso8601String(),
'created': created?.toIso8601String(),
'reminder_dates':
reminders?.map((date) => date.toIso8601String())?.toList(),
'due_date': due?.toIso8601String(),
'updated': updated?.millisecondsSinceEpoch,
'created': created?.millisecondsSinceEpoch,
'reminderDate': reminder?.millisecondsSinceEpoch,
'dueDate': due?.millisecondsSinceEpoch,
'description': description,
'title': title,
'text': text,
'done': done ?? false,
'created_by': owner?.toJSON()
'createdBy': owner?.toJSON()
};
}
class TaskList {
final int id;
final String title, description;
final User owner;
final DateTime created, updated;
final List<Task> tasks;
TaskList(
{@required this.id,
@required this.title,
this.description,
this.owner,
this.created,
this.updated,
@required this.tasks});
TaskList.fromJson(Map<String, dynamic> json)
: id = json['id'],
owner = User.fromJson(json['owner']),
description = json['description'],
title = json['title'],
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
tasks = (json['tasks'] as List<dynamic>)
?.map((taskJson) => Task.fromJson(taskJson))
?.toList();
toJSON() {
return {
"created": this.created?.millisecondsSinceEpoch,
"updated": this.updated?.millisecondsSinceEpoch,
"id": this.id,
"title": this.title,
"owner": this.owner?.toJSON()
};
}
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:vikunja_app/global.dart';
import 'package:meta/meta.dart';
class User {
final int id;
@ -8,17 +7,10 @@ class User {
User(this.id, this.email, this.username);
User.fromJson(Map<String, dynamic> json)
: id = json['id'],
email = json.containsKey('email') ? json['email'] : '',
email = json['email'],
username = json['username'];
toJSON() => {"id": this.id, "email": this.email, "username": this.username};
String avatarUrl(BuildContext context) {
return VikunjaGlobal.of(context).client.base +
"/" +
this.username +
"/avatar";
}
}
class UserTokenPair {

View File

@ -1,178 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:after_layout/after_layout.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/ErrorDialog.dart';
import 'package:vikunja_app/pages/namespace/namespace.dart';
import 'package:vikunja_app/pages/namespace/namespace_edit.dart';
import 'package:vikunja_app/pages/placeholder.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/namespace.dart';
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new HomePageState();
}
class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
List<Namespace> _namespaces = [];
Namespace get _currentNamespace =>
_selectedDrawerIndex >= 0 && _selectedDrawerIndex < _namespaces.length
? _namespaces[_selectedDrawerIndex]
: null;
int _selectedDrawerIndex = -1;
bool _loading = true;
bool _showUserDetails = false;
@override
void afterFirstLayout(BuildContext context) {
_loadNamespaces();
}
Widget _namespacesWidget() {
List<Widget> namespacesList = <Widget>[];
_namespaces
.asMap()
.forEach((i, namespace) => namespacesList.add(new ListTile(
leading: const Icon(Icons.folder),
title: new Text(namespace.title),
selected: i == _selectedDrawerIndex,
onTap: () => _onSelectItem(i),
)));
return this._loading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(
context: context, tiles: namespacesList)
.toList()),
onRefresh: _loadNamespaces,
);
}
Widget _userDetailsWidget(BuildContext context) {
return ListView(padding: EdgeInsets.zero, children: <Widget>[
ListTile(
title: Text('Logout'),
leading: Icon(Icons.exit_to_app),
onTap: () {
VikunjaGlobal.of(context).logoutUser(context);
},
),
]);
}
@override
Widget build(BuildContext context) {
var currentUser = VikunjaGlobal.of(context).currentUser;
return new Scaffold(
appBar: AppBar(
title: new Text(_currentNamespace?.title ?? 'Vikunja'),
actions: _currentNamespace == null
? null
: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NamespaceEditPage(
namespace: _currentNamespace,
))))
],
),
drawer: new Drawer(
child: new Column(children: <Widget>[
new UserAccountsDrawerHeader(
accountEmail:
currentUser?.email == null ? null : Text(currentUser.email),
accountName:
currentUser?.username == null ? null : Text(currentUser.username),
onDetailsPressed: () {
setState(() {
_showUserDetails = !_showUserDetails;
});
},
currentAccountPicture: currentUser == null
? null
: CircleAvatar(
backgroundImage: NetworkImage(currentUser.avatarUrl(context)),
),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/graphics/hypnotize.png"),
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
Theme.of(context).primaryColor, BlendMode.multiply)),
),
),
new Builder(
builder: (BuildContext context) => Expanded(
child: _showUserDetails
? _userDetailsWidget(context)
: _namespacesWidget())),
new Align(
alignment: FractionalOffset.bottomCenter,
child: Builder(
builder: (context) => ListTile(
leading: const Icon(Icons.add),
title: const Text('Add namespace...'),
onTap: () => _addNamespaceDialog(context),
),
),
),
])),
body: _getDrawerItemWidget(_selectedDrawerIndex),
);
}
_getDrawerItemWidget(int pos) {
if (pos == -1) {
return new PlaceholderPage();
}
return new NamespacePage(namespace: _namespaces[pos]);
}
_onSelectItem(int index) {
setState(() => _selectedDrawerIndex = index);
Navigator.of(context).pop();
}
_addNamespaceDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: (name) => _addNamespace(name, context),
decoration: new InputDecoration(
labelText: 'Namespace', hintText: 'eg. Personal Namespace'),
));
}
_addNamespace(String name, BuildContext context) {
VikunjaGlobal.of(context)
.namespaceService
.create(Namespace(id: null, title: name))
.then((_) {
_loadNamespaces();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('The namespace was created successfully!'),
));
}).catchError((error) => showDialog(
context: context, builder: (context) => ErrorDialog(error: error)));
}
Future<void> _loadNamespaces() {
return VikunjaGlobal.of(context).namespaceService.getAll().then((result) {
setState(() {
_loading = false;
_namespaces = result;
});
});
}
}

126
lib/pages/home_page.dart Normal file
View File

@ -0,0 +1,126 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/GravatarImage.dart';
import 'package:vikunja_app/fragments/namespace.dart';
import 'package:vikunja_app/fragments/placeholder.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/models/user.dart';
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
List<Namespace> _namespaces = [];
Namespace get _currentNamespace =>
_selectedDrawerIndex >= 0 && _selectedDrawerIndex < _namespaces.length
? _namespaces[_selectedDrawerIndex]
: null;
int _selectedDrawerIndex = -1;
bool _loading = true;
_getDrawerItemWidget(int pos) {
if (pos == -1) {
return new PlaceholderFragment();
}
return new NamespaceFragment(namespace: _namespaces[pos]);
}
_onSelectItem(int index) {
setState(() => _selectedDrawerIndex = index);
Navigator.of(context).pop();
}
_addNamespaceDialog() {
showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: _addNamespace,
decoration: new InputDecoration(
labelText: 'Namespace', hintText: 'eg. Personal Namespace'),
));
}
_addNamespace(String name) {
VikunjaGlobal.of(context)
.namespaceService
.create(Namespace(id: null, name: name))
.then((_) => _updateNamespaces());
}
Future<void> _updateNamespaces() {
return VikunjaGlobal.of(context).namespaceService.getAll().then((result) {
setState(() {
_loading = false;
_namespaces = result;
});
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_updateNamespaces();
}
@override
Widget build(BuildContext context) {
var currentUser = VikunjaGlobal.of(context).currentUser;
List<Widget> drawerOptions = <Widget>[];
_namespaces
.asMap()
.forEach((i, namespace) => drawerOptions.add(new ListTile(
leading: const Icon(Icons.folder),
title: new Text(namespace.name),
selected: i == _selectedDrawerIndex,
onTap: () => _onSelectItem(i),
)));
return new Scaffold(
appBar: AppBar(title: new Text(_currentNamespace?.name ?? 'Vikunja')),
drawer: new Drawer(
child: new Column(children: <Widget>[
new UserAccountsDrawerHeader(
accountEmail: currentUser == null ? null : Text(currentUser.email),
accountName: currentUser == null ? null : Text(currentUser.username),
currentAccountPicture: currentUser == null
? null
: CircleAvatar(
backgroundImage: GravatarImageProvider(currentUser.username)),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/graphics/hypnotize.png"),
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
Theme.of(context).primaryColor, BlendMode.multiply)),
),
),
new Expanded(
child: this._loading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(
context: context, tiles: drawerOptions)
.toList()),
onRefresh: _updateNamespaces,
)),
new Align(
alignment: FractionalOffset.bottomCenter,
child: new ListTile(
leading: const Icon(Icons.add),
title: const Text('Add namespace...'),
onTap: () => _addNamespaceDialog(),
),
),
])),
body: _getDrawerItemWidget(_selectedDrawerIndex),
);
}
}

View File

@ -1,128 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/TaskTile.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/pages/list/list_edit.dart';
class ListPage extends StatefulWidget {
final TaskList taskList;
ListPage({this.taskList}) : super(key: Key(taskList.id.toString()));
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
TaskList _list;
List<Task> _loadingTasks = [];
bool _loading = true;
@override
void initState() {
_list = TaskList(
id: widget.taskList.id, title: widget.taskList.title, tasks: []);
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_loadList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text(_list.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListEditPage(
list: _list,
))))
],
),
body: !this._loading
? RefreshIndicator(
child: _list.tasks.length > 0
? ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children: ListTile.divideTiles(
context: context, tiles: _listTasks())
.toList(),
)
: Center(child: Text('This list is empty.')),
onRefresh: _loadList,
)
: Center(child: CircularProgressIndicator()),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => _addItemDialog(context), child: Icon(Icons.add)),
));
}
List<Widget> _listTasks() {
var tasks = (_list?.tasks?.map(_buildTile) ?? []).toList();
tasks.addAll(_loadingTasks.map(_buildLoadingTile));
return tasks;
}
TaskTile _buildTile(Task task) {
return TaskTile(task: task, loading: false);
}
TaskTile _buildLoadingTile(Task task) {
return TaskTile(
task: task,
loading: true,
);
}
Future<void> _loadList() {
return VikunjaGlobal.of(context)
.listService
.get(widget.taskList.id)
.then((list) {
setState(() {
_loading = false;
_list = list;
});
});
}
_addItemDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: (name) => _addItem(name, context),
decoration: new InputDecoration(
labelText: 'Task Name', hintText: 'eg. Milk')));
}
_addItem(String name, BuildContext context) {
var globalState = VikunjaGlobal.of(context);
var newTask = Task(
id: null, title: name, owner: globalState.currentUser, done: false);
setState(() => _loadingTasks.add(newTask));
globalState.taskService.add(_list.id, newTask).then((task) {
setState(() {
_list.tasks.add(task);
});
}).then((_) {
_loadList();
setState(() => _loadingTasks.remove(newTask));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('The task was added successfully!'),
));
});
}
}

View File

@ -1,119 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/theme/button.dart';
import 'package:vikunja_app/theme/buttonText.dart';
class ListEditPage extends StatefulWidget {
final TaskList list;
ListEditPage({this.list}) : super(key: Key(list.toString()));
@override
State<StatefulWidget> createState() => _ListEditPageState();
}
class _ListEditPageState extends State<ListEditPage> {
final _formKey = GlobalKey<FormState>();
bool _loading = false;
String _title, _description;
@override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('Edit List'),
),
body: Builder(
builder: (BuildContext context) => SafeArea(
child: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
maxLines: null,
keyboardType: TextInputType.multiline,
initialValue: widget.list.title,
onSaved: (title) => _title = title,
validator: (title) {
if (title.length < 3 || title.length > 250) {
return 'The title needs to have between 3 and 250 characters.';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Title',
border: OutlineInputBorder(),
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
maxLines: null,
keyboardType: TextInputType.multiline,
initialValue: widget.list.description,
onSaved: (description) => _description = description,
validator: (description) {
if (description.length > 1000) {
return 'The description can have a maximum of 1000 characters.';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(),
),
),
),
Builder(
builder: (context) => Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: FancyButton(
onPressed: !_loading
? () {
if (_formKey.currentState.validate()) {
Form.of(context).save();
_saveList(context);
}
}
: null,
child: _loading
? CircularProgressIndicator()
: VikunjaButtonText('Save'),
))),
]),
),
),
),
);
}
_saveList(BuildContext context) async {
setState(() => _loading = true);
// FIXME: is there a way we can update the list without creating a new list object?
// aka updating the existing list we got from context (setters?)
TaskList updatedList =
TaskList(id: widget.list.id, title: _title, description: _description);
VikunjaGlobal.of(context).listService.update(updatedList).then((_) {
setState(() => _loading = false);
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('The list was updated successfully!'),
));
}).catchError((err) {
setState(() => _loading = false);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Something went wrong: ' + err.toString()),
action: SnackBarAction(
label: 'CLOSE',
onPressed: Scaffold.of(context).hideCurrentSnackBar),
),
);
});
}
}

114
lib/pages/list_page.dart Normal file
View File

@ -0,0 +1,114 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/TaskTile.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/task.dart';
class ListPage extends StatefulWidget {
final TaskList taskList;
ListPage({this.taskList}) : super(key: Key(taskList.id.toString()));
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
TaskList _items;
List<Task> _loadingTasks = [];
bool _loading = true;
@override
void initState() {
_items = TaskList(
id: widget.taskList.id, title: widget.taskList.title, tasks: []);
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_updateList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text(_items.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => {/* TODO add edit list functionality */},
)
],
),
body: !this._loading
? RefreshIndicator(
child: ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children:
ListTile.divideTiles(context: context, tiles: _listTasks())
.toList(),
),
onRefresh: _updateList,
)
: Center(child: CircularProgressIndicator()),
floatingActionButton: FloatingActionButton(
onPressed: () => _addItemDialog(), child: Icon(Icons.add)),
);
}
List<Widget> _listTasks() {
var tasks = (_items?.tasks?.map(_buildTile) ?? []).toList();
tasks.addAll(_loadingTasks.map(_buildLoadingTile));
return tasks;
}
TaskTile _buildTile(Task task) {
return TaskTile(task: task, loading: false);
}
TaskTile _buildLoadingTile(Task task) {
return TaskTile(
task: task,
loading: true,
);
}
Future<void> _updateList() {
return VikunjaGlobal.of(context)
.listService
.get(widget.taskList.id)
.then((tasks) {
setState(() {
_loading = false;
_items = tasks;
});
});
}
_addItemDialog() {
showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: _addItem,
decoration: new InputDecoration(
labelText: 'List Item', hintText: 'eg. Milk')));
}
_addItem(String name) {
var globalState = VikunjaGlobal.of(context);
var newTask =
Task(id: null, text: name, owner: globalState.currentUser, done: false);
setState(() => _loadingTasks.add(newTask));
globalState.taskService.add(_items.id, newTask).then((task) {
setState(() {
_items.tasks.add(task);
});
}).then((_) => _updateList()
.then((_) => setState(() => _loadingTasks.remove(newTask))));
}
}

129
lib/pages/login_page.dart Normal file
View File

@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/pages/register_page.dart';
import 'package:vikunja_app/utils/validator.dart';
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
String _server, _username, _password;
bool _loading = false;
@override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('Login to Vikunja'),
),
body: Builder(
builder: (BuildContext context) => SafeArea(
top: false,
bottom: false,
child: Form(
autovalidate: true,
key: _formKey,
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Image(
image: AssetImage('assets/vikunja_logo.png'),
height: 128.0,
semanticLabel: 'Vikunja Logo',
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
onSaved: (serverAddress) => _server = serverAddress,
validator: (address) {
return isUrl(address) ? null : 'Invalid URL';
},
decoration: new InputDecoration(
labelText: 'Server Address'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onSaved: (username) => _username = username,
decoration:
new InputDecoration(labelText: 'Username'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onSaved: (password) => _password = password,
decoration:
new InputDecoration(labelText: 'Password'),
obscureText: true,
),
),
Builder(
builder: (context) => ButtonTheme(
height: _loading ? 55.0 : 36.0,
child: RaisedButton(
onPressed: !_loading
? () {
if (_formKey.currentState
.validate()) {
Form.of(context).save();
_loginUser(context);
}
}
: null,
child: _loading
? CircularProgressIndicator()
: Text('Login'),
))),
Builder(
builder: (context) => ButtonTheme(
height: _loading ? 55.0 : 36.0,
child: RaisedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RegisterPage())),
child: _loading
? CircularProgressIndicator()
: Text('Register'),
))),
],
)),
),
));
}
_loginUser(BuildContext context) async {
setState(() => _loading = true);
try {
var vGlobal = VikunjaGlobal.of(context);
var newUser =
await vGlobal.newUserService(_server).login(_username, _password);
vGlobal.changeUser(newUser.user, token: newUser.token, base: _server);
} catch (ex) {
showDialog(
context: context,
builder: (context) => new AlertDialog(
title:
const Text('Login failed! Please check you credentials.'),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text('CLOSE'))
],
));
} finally {
setState(() {
_loading = false;
});
}
}
}

View File

@ -1,130 +0,0 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:after_layout/after_layout.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/pages/list/list.dart';
class NamespacePage extends StatefulWidget {
final Namespace namespace;
NamespacePage({this.namespace}) : super(key: Key(namespace.id.toString()));
@override
_NamespacePageState createState() => new _NamespacePageState();
}
class _NamespacePageState extends State<NamespacePage>
with AfterLayoutMixin<NamespacePage> {
List<TaskList> _lists = [];
bool _loading = true;
@override
void afterFirstLayout(BuildContext context) {
_loadLists();
}
/////
// This essentially shows the lists.
@override
Widget build(BuildContext context) {
return Scaffold(
body: !this._loading
? RefreshIndicator(
child: _lists.length > 0
? new ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children: ListTile.divideTiles(
context: context,
tiles: _lists.map((ls) => Dismissible(
key: Key(ls.id.toString()),
direction: DismissDirection.startToEnd,
child: ListTile(
title: new Text(ls.title),
onTap: () => _openList(context, ls),
trailing: Icon(Icons.arrow_right),
),
background: Container(
color: Colors.red,
child: const ListTile(
leading: Icon(Icons.delete,
color: Colors.white, size: 36.0)),
),
onDismissed: (direction) {
_removeList(ls).then((_) => Scaffold.of(
context)
.showSnackBar(SnackBar(
content:
Text("${ls.title} removed"))));
},
))).toList(),
)
: Center(child: Text('This namespace is empty.')),
onRefresh: _loadLists,
)
: Center(child: CircularProgressIndicator()),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => _addListDialog(context),
child: const Icon(Icons.add))),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_loadLists();
}
Future _removeList(TaskList list) {
return VikunjaGlobal.of(context)
.listService
.delete(list.id)
.then((_) => _loadLists());
}
Future<void> _loadLists() {
return VikunjaGlobal.of(context)
.listService
.getByNamespace(widget.namespace.id)
.then((lists) => setState(() {
this._lists = lists;
this._loading = false;
}));
}
_openList(BuildContext context, TaskList list) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ListPage(taskList: list)));
}
_addListDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: (name) => _addList(name, context),
decoration: new InputDecoration(
labelText: 'List Name', hintText: 'eg. Shopping List')),
);
}
_addList(String name, BuildContext context) {
VikunjaGlobal.of(context)
.listService
.create(widget.namespace.id, TaskList(id: null, title: name, tasks: []))
.then((_) {
setState(() {});
_loadLists();
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('The list was successfully created!'),
),
);
});
}
}

View File

@ -1,125 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/theme/button.dart';
import 'package:vikunja_app/theme/buttonText.dart';
class NamespaceEditPage extends StatefulWidget {
final Namespace namespace;
NamespaceEditPage({this.namespace}) : super(key: Key(namespace.toString()));
@override
State<StatefulWidget> createState() => _NamespaceEditPageState();
}
class _NamespaceEditPageState extends State<NamespaceEditPage> {
final _formKey = GlobalKey<FormState>();
bool _loading = false;
String _name, _description;
@override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Namespace'),
),
body: Builder(
builder: (BuildContext context) => SafeArea(
child: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
maxLines: null,
keyboardType: TextInputType.multiline,
initialValue: widget.namespace.title,
onSaved: (name) => _name = name,
validator: (name) {
if (name.length < 3 || name.length > 250) {
return 'The name needs to have between 3 and 250 characters.';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
maxLines: null,
keyboardType: TextInputType.multiline,
initialValue: widget.namespace.description,
onSaved: (description) => _description = description,
validator: (description) {
if (description.length > 1000) {
return 'The description can have a maximum of 1000 characters.';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(),
),
),
),
Builder(
builder: (context) => Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: FancyButton(
onPressed: !_loading
? () {
if (_formKey.currentState.validate()) {
Form.of(context).save();
_saveNamespace(context);
}
}
: null,
child: _loading
? CircularProgressIndicator()
: VikunjaButtonText('Save'),
))),
]),
),
),
),
);
}
_saveNamespace(BuildContext context) async {
setState(() => _loading = true);
// FIXME: is there a way we can update the namespace without creating a new namespace object?
// aka updating the existing namespace we got from context (setters?)
Namespace updatedNamespace = Namespace(
id: widget.namespace.id,
title: _name,
description: _description,
owner: widget.namespace.owner);
VikunjaGlobal.of(context)
.namespaceService
.update(updatedNamespace)
.then((_) {
setState(() => _loading = false);
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('The namespace was updated successfully!'),
));
}).catchError((err) {
setState(() => _loading = false);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Something went wrong: ' + err.toString()),
action: SnackBarAction(
label: 'CLOSE',
onPressed: Scaffold.of(context).hideCurrentSnackBar),
),
);
});
}
}

View File

@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/utils/validator.dart';
class RegisterPage extends StatefulWidget {
@override
_RegisterPageState createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
final _formKey = GlobalKey<FormState>();
final passwordController = TextEditingController();
String _server, _username, _email, _password;
bool _loading = false;
@override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('Register to Vikunja'),
),
body: Builder(
builder: (BuildContext context) => SafeArea(
top: false,
bottom: false,
child: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Image(
image: AssetImage('assets/vikunja_logo.png'),
height: 128.0,
semanticLabel: 'Vikunja Logo',
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
onSaved: (serverAddress) => _server = serverAddress,
validator: (address) {
return isUrl(address) ? null : 'Invalid URL';
},
decoration: new InputDecoration(
labelText: 'Server Address'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onSaved: (username) => _username = username.trim(),
validator: (username) {
return username.trim().isNotEmpty ? null : 'Please specify a username';
},
decoration:
new InputDecoration(labelText: 'Username'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onSaved: (email) => _email = email,
validator: (email) {
return isEmail(email)
? null
: 'Email adress is invalid';
},
decoration:
new InputDecoration(labelText: 'Email Address'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: passwordController,
onSaved: (password) => _password = password,
validator: (password) {
return password.length >= 8 ? null : 'Please use at least 8 characters';
},
decoration:
new InputDecoration(labelText: 'Password'),
obscureText: true,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (password) {
return passwordController.text == password
? null
: 'Passwords don\'t match.';
},
decoration: new InputDecoration(
labelText: 'Repeat Password'),
obscureText: true,
),
),
Builder(
builder: (context) => ButtonTheme(
height: _loading ? 55.0 : 36.0,
child: RaisedButton(
onPressed: !_loading
? () {
if (_formKey.currentState
.validate()) {
Form.of(context).save();
_registerUser(context);
} else {
print("awhat");
}
}
: null,
child: _loading
? CircularProgressIndicator()
: Text('Register'),
))),
],
)),
),
));
}
_registerUser(BuildContext context) async {
setState(() => _loading = true);
try {
var vGlobal = VikunjaGlobal.of(context);
var newUserLoggedIn = await vGlobal
.newUserService(_server)
.register(_username, _email, _password);
vGlobal.changeUser(newUserLoggedIn.user,
token: newUserLoggedIn.token, base: _server);
} catch (ex) {
showDialog(
context: context,
builder: (context) => new AlertDialog(
title: const Text(
'Registration failed! Please check your server url and credentials.'),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text('CLOSE'))
],
));
} finally {
setState(() {
_loading = false;
});
}
}
}

View File

@ -1,136 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/pages/user/register.dart';
import 'package:vikunja_app/theme/button.dart';
import 'package:vikunja_app/theme/buttonText.dart';
import 'package:vikunja_app/theme/constants.dart';
import 'package:vikunja_app/utils/validator.dart';
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
String _server, _username, _password;
bool _loading = false;
@override
Widget build(BuildContext ctx) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Builder(
builder: (BuildContext context) => Form(
autovalidate: true,
key: _formKey,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 30),
child: Image(
image: Theme.of(context).brightness == Brightness.dark
? AssetImage('assets/vikunja_logo_full_white.png')
: AssetImage('assets/vikunja_logo_full.png'),
height: 85.0,
semanticLabel: 'Vikunja Logo',
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
enabled: !_loading,
onSaved: (serverAddress) => _server = serverAddress,
validator: (address) {
return isUrl(address) ? null : 'Invalid URL';
},
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Server Address'),
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
enabled: !_loading,
onSaved: (username) => _username = username,
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Username'),
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
enabled: !_loading,
onSaved: (password) => _password = password,
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password'),
obscureText: true,
),
),
Builder(
builder: (context) => FancyButton(
onPressed: !_loading
? () {
if (_formKey.currentState.validate()) {
Form.of(context).save();
_loginUser(context);
}
}
: null,
child: _loading
? CircularProgressIndicator()
: VikunjaButtonText('Login'),
)),
Builder(
builder: (context) => FancyButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterPage())),
child: VikunjaButtonText('Register'),
)),
],
),
),
),
),
),
),
);
}
_loginUser(BuildContext context) async {
setState(() => _loading = true);
try {
var vGlobal = VikunjaGlobal.of(context);
var newUser =
await vGlobal.newUserService(_server).login(_username, _password);
vGlobal.changeUser(newUser.user, token: newUser.token, base: _server);
} catch (ex) {
showDialog(
context: context,
builder: (context) => new AlertDialog(
title: Text(
'Login failed! Please check your server url and credentials. ' +
ex.toString()),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'))
],
));
} finally {
setState(() {
_loading = false;
});
}
}
}

View File

@ -1,154 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/theme/button.dart';
import 'package:vikunja_app/theme/buttonText.dart';
import 'package:vikunja_app/theme/constants.dart';
import 'package:vikunja_app/utils/validator.dart';
class RegisterPage extends StatefulWidget {
@override
_RegisterPageState createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
final _formKey = GlobalKey<FormState>();
final passwordController = TextEditingController();
String _server, _username, _email, _password;
bool _loading = false;
@override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('Register'),
),
body: Builder(
builder: (BuildContext context) => SafeArea(
top: false,
bottom: false,
child: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
onSaved: (serverAddress) => _server = serverAddress,
validator: (address) {
return isUrl(address) ? null : 'Invalid URL';
},
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Server Address'),
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
onSaved: (username) => _username = username.trim(),
validator: (username) {
return username.trim().isNotEmpty
? null
: 'Please specify a username';
},
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Username'),
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
onSaved: (email) => _email = email,
validator: (email) {
return isEmail(email)
? null
: 'Email adress is invalid';
},
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email Address'),
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
controller: passwordController,
onSaved: (password) => _password = password,
validator: (password) {
return password.length >= 8
? null
: 'Please use at least 8 characters';
},
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password'),
obscureText: true,
),
),
Padding(
padding: vStandardVerticalPadding,
child: TextFormField(
validator: (password) {
return passwordController.text == password
? null
: 'Passwords don\'t match.';
},
decoration: new InputDecoration(
border: OutlineInputBorder(),
labelText: 'Repeat Password'),
obscureText: true,
),
),
Builder(
builder: (context) => FancyButton(
onPressed: !_loading
? () {
if (_formKey.currentState.validate()) {
Form.of(context).save();
_registerUser(context);
} else {
print("awhat");
}
}
: null,
child: _loading
? CircularProgressIndicator()
: VikunjaButtonText('Register'),
)),
],
)),
),
));
}
_registerUser(BuildContext context) async {
setState(() => _loading = true);
try {
var vGlobal = VikunjaGlobal.of(context);
var newUserLoggedIn = await vGlobal
.newUserService(_server)
.register(_username, _email, _password);
vGlobal.changeUser(newUserLoggedIn.user,
token: newUserLoggedIn.token, base: _server);
} catch (ex) {
showDialog(
context: context,
builder: (context) => new AlertDialog(
title: Text(
'Registration failed! Please check your server url and credentials. ' +
ex.toString()),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'))
],
));
} finally {
setState(() {
_loading = false;
});
}
}
}

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/models/user.dart';
@ -12,7 +11,7 @@ var _users = {1: User(1, 'test@testuser.org', 'test1')};
var _namespaces = {
1: Namespace(
id: 1,
title: 'Test 1',
name: 'Test 1',
created: DateTime.now(),
updated: DateTime.now(),
description: 'A namespace for testing purposes',
@ -38,7 +37,7 @@ var _lists = {
var _tasks = {
1: Task(
id: 1,
title: 'Task 1',
text: 'Task 1',
owner: _users[1],
updated: DateTime.now(),
created: DateTime.now(),

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/models/user.dart';

23
lib/style.dart Normal file
View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
const vBlue = Color(0xFF455486);
const vBlueLight = Color(0xFF7480b7);
const vBlueDark = Color(0xFF152c5a);
const vBlack = Color(0xFFFFFFFF);
ThemeData buildVikunjaTheme() {
var base = ThemeData.light();
return base.copyWith(
primaryColor: vBlue,
primaryColorLight: vBlueLight,
primaryColorDark: vBlueDark,
textTheme: base.textTheme.copyWith(
headline: base.textTheme.headline.copyWith(
fontFamily: 'Quicksand',
fontSize: 72.0,
),
title: base.textTheme.title.copyWith(
fontFamily: 'Quicksand',
),
));
}

View File

@ -1,45 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/theme/constants.dart';
class FancyButton extends StatelessWidget {
final double width;
final double height;
final Function onPressed;
final Widget child;
const FancyButton({
Key key,
@required this.child,
this.width = double.infinity,
this.height = 35,
this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: vStandardVerticalPadding,
child: Container(
width: width,
height: height,
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Theme.of(context).brightness == Brightness.dark
? vButtonShadowDark
: vButtonShadow,
offset: Offset(-5, 5),
blurRadius: 10,
),
]),
child: Material(
borderRadius: BorderRadius.circular(3),
color: vButtonColor,
child: InkWell(
onTap: onPressed,
child: Center(
child: child,
)),
),
));
}
}

View File

@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/theme/constants.dart';
class VikunjaButtonText extends StatelessWidget {
final String text;
const VikunjaButtonText(
this.text, {
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
text,
style: TextStyle(color: vButtonTextColor, fontWeight: FontWeight.w600),
);
}
}

View File

@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
/////////
// Colors
//////
const vBlue = Color(0xFF7F23FF);
const vBlueLight = Color(0xFF7480b7);
const vBlueDark = Color(0xFF152c5a);
const vPrimary = Color(0xFF0c86ff);
const vPrimaryDark = Color(0xFF1A73E8);
const vRed = Color(0xFFff4136);
const vOrange = Color(0xFFff851b);
const vWhite = Color(0xFFffffff);
const vGreen = Color(0xFF00CE6E);
const vButtonColor = vPrimary;
const vButtonTextColor = vWhite;
const vButtonShadowDark = Color(0xFF0b2a4a);
const vButtonShadow = Color(0xFFb2d9ff);
///////////
// Paddings
////////
const vStandardVerticalPadding = EdgeInsets.symmetric(vertical: 5.0);
const vStandardHorizontalPadding = EdgeInsets.symmetric(horizontal: 5.0);
const vStandardPadding = EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0);

View File

@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
class FancyAppBar extends StatelessWidget {
final String title;
final double barHeight = 80.0;
FancyAppBar(this.title);
@override
Widget build(BuildContext context) {
return new Container(
height: barHeight,
width: double.infinity,
decoration: new BoxDecoration(color: Colors.blue),
child: new Padding(
padding: EdgeInsets.symmetric(vertical: 38, horizontal: 10),
child: new Text(title,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontFamily: 'Quicksand',
fontSize: 21)),
),
);
}
}
/*
Usage:
return new Scaffold(
body: new Column(
children: <Widget>[
new FancyAppBar('Login to Vikunja'),
],
),
);
*/

View File

@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
import 'package:vikunja_app/theme/constants.dart';
ThemeData buildVikunjaTheme() => _buildVikunjaTheme(ThemeData.light());
ThemeData buildVikunjaDarkTheme() {
ThemeData base = _buildVikunjaTheme(ThemeData.dark());
return base.copyWith(
accentColor: vWhite,
);
}
ThemeData _buildVikunjaTheme(ThemeData base) {
return base.copyWith(
errorColor: vRed,
primaryColor: vPrimaryDark,
primaryColorLight: vPrimary,
primaryColorDark: vBlueDark,
buttonTheme: base.buttonTheme.copyWith(
buttonColor: vPrimary,
textTheme: ButtonTextTheme.normal,
colorScheme: base.buttonTheme.colorScheme.copyWith(
// Why does this not work?
onSurface: vWhite,
onSecondary: vWhite,
background: vBlue,
),
),
textTheme: base.textTheme.copyWith(
headline: base.textTheme.headline.copyWith(
fontFamily: 'Quicksand',
),
title: base.textTheme.title.copyWith(
fontFamily: 'Quicksand',
),
button: base.textTheme.button.copyWith(
color:
vWhite, // This does not work, looks like a bug in Flutter: https://github.com/flutter/flutter/issues/19623
),
),
);
}

View File

@ -1,252 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
after_layout:
dependency: "direct main"
description:
name: after_layout
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.7+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: "1.6.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0-nullsafety.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.3"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0-nullsafety.3"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.1"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.5"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: "direct main"
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.0+3"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.3"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.12"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10-nullsafety.1"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0-nullsafety.3"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0-nullsafety.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0+1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0-nullsafety.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0-nullsafety.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19-nullsafety.2"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0-nullsafety.3"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.3"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.1"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
sdks:
dart: ">=2.10.0-110 <2.11.0"
flutter: ">=1.20.0 <2.0.0"

View File

@ -1,23 +1,25 @@
name: vikunja_app
description: Vikunja as Flutter cross platform app
version: 0.1.0
version: 0.0.1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.0.0-dev.63.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.3
flutter_secure_storage: 3.3.5
http: 0.12.0+3
after_layout: ^1.0.7
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
flutter_secure_storage: 3.1.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: "^0.8.0"
flutter_launcher_icons: "^0.6.1"
flutter_icons:
image_path: "assets/vikunja_logo.png"
@ -27,14 +29,54 @@ flutter_icons:
adaptive_icon_background: "#FF455486"
adaptive_icon_foreground: "assets/vikunja_logo_adaptive.png"
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/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:
# - 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.io/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages
assets:
- assets/graphics/background.jpg
- assets/graphics/hypnotize.png
- assets/vikunja_logo.png
- assets/vikunja_logo_full.png
- assets/vikunja_logo_full_white.png
fonts:
- family: Quicksand
fonts:
- asset: assets/fonts/quicksand-v7-latin-regular.ttf

View File

@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}