Compare commits

...

9 Commits

41 changed files with 512 additions and 1719 deletions

View File

@ -4,5 +4,27 @@
# This file should be version controlled and should not be manually edited.
version:
revision: 3b309bda072a6b326e8aa4591a5836af600923ce
channel: beta
revision: "300451adae589accbece3490f4396f10bdf15e6e"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
- platform: web
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

29
.vscode/launch.json vendored
View File

@ -1,14 +1,19 @@
{
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"args": [
"--flavor",
"main"
],
}
]
"configurations": [
{
"name": "Flutter (Chromium)",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"args": ["-d", "chrome", "--flavor", "main"],
"deviceId": "chrome",
},
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"args": ["--flavor", "main"]
}
]
}

View File

@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 33
compileSdkVersion 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@ -42,7 +42,7 @@ android {
defaultConfig {
applicationId "io.vikunja.app"
minSdkVersion 21
targetSdkVersion 33
targetSdkVersion 34
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@ -21,7 +21,9 @@
android:label="Vikunja"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
android:networkSecurityConfig="@xml/network_security_config"
>
<meta-data android:name="io.flutter.network-policy" android:resource="@xml/network_security_config"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
@ -106,4 +108,4 @@
</provider>
</application>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>
</manifest>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.20'
ext.kotlin_version = '1.9.23'
repositories {
google()
mavenCentral()

0
android/gradlew vendored Normal file → Executable file
View File

View File

@ -1,9 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'dart:io';
import 'package:cronet_http/cronet_http.dart' as cronet_http;
import 'package:cupertino_http/cupertino_http.dart' as cupertino_http;
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as io_client;
import 'package:vikunja_app/api/response.dart';
import 'package:vikunja_app/components/string_extension.dart';
import 'package:vikunja_app/global.dart';
@ -25,16 +27,42 @@ class Client {
String? post_body;
bool operator ==(dynamic otherClient) {
@override
bool operator ==(Object otherClient) {
if (otherClient is! Client) return false;
return otherClient._token == _token;
}
Client(this.global_scaffold_key,
{String? token, String? base, bool authenticated = false}) {
configure(token: token, base: base, authenticated: authenticated);
Client(
this.global_scaffold_key, {
String? token,
String? base,
bool authenticated = false,
}) {
configure(
token: token,
base: base,
authenticated: authenticated,
);
}
void reload_ignore_certs(bool? val) {
http.Client get httpClient {
if (Platform.isAndroid) {
final engine = cronet_http.CronetEngine.build(
cacheMode: cronet_http.CacheMode.memory, cacheMaxSize: 1000000);
return cronet_http.CronetClient.fromCronetEngine(engine);
}
if (Platform.isIOS || Platform.isMacOS) {
final config =
cupertino_http.URLSessionConfiguration.ephemeralSessionConfiguration()
..cache =
cupertino_http.URLCache.withCapacity(memoryCapacity: 1000000);
return cupertino_http.CupertinoClient.fromSessionConfiguration(config);
}
return io_client.IOClient();
}
void reloadIgnoreCerts(bool? val) {
ignoreCertificates = val ?? false;
HttpOverrides.global = new IgnoreCertHttpOverrides(ignoreCertificates);
if (global_scaffold_key == null ||
@ -47,7 +75,7 @@ class Client {
get _headers => {
'Authorization': _token != '' ? 'Bearer $_token' : '',
'Content-Type': 'application/json',
'User-Agent': 'Vikunja Mobile App'
'User-Agent': 'Vikunja Mobile App',
};
get headers => _headers;
@ -55,7 +83,11 @@ class Client {
@override
int get hashCode => _token.hashCode;
void configure({String? token, String? base, bool? authenticated}) {
void configure({
String? token,
String? base,
bool? authenticated,
}) {
if (token != null) _token = token;
if (base != null) {
base = base.replaceAll(" ", "");
@ -66,7 +98,6 @@ class Client {
}
void reset() {
_token = _base = '';
authenticated = false;
}
@ -85,14 +116,14 @@ class Client {
queryParameters: queryParameters,
fragment: uri.fragment);
return http
return httpClient
.get(uri, headers: _headers)
.then(_handleResponse)
.onError((error, stackTrace) => _handleError(error, stackTrace));
}
Future<Response?> delete(String url) {
return http
return httpClient
.delete(
'${this.base}$url'.toUri()!,
headers: _headers,
@ -102,7 +133,7 @@ class Client {
}
Future<Response?> post(String url, {dynamic body}) {
return http
return httpClient
.post(
'${this.base}$url'.toUri()!,
headers: _headers,
@ -113,7 +144,7 @@ class Client {
}
Future<Response?> put(String url, {dynamic body}) {
return http
return httpClient
.put(
'${this.base}$url'.toUri()!,
headers: _headers,
@ -183,7 +214,7 @@ class Client {
}
Response? _handleResponse(http.Response response) {
Error? error = _handleResponseErrors(response);
_handleResponseErrors(response);
return Response(
_decoder.convert(response.body), response.statusCode, response.headers);
}

View File

@ -1,108 +0,0 @@
import 'dart:async';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
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/service/services.dart';
class ListAPIService extends APIService implements ListService {
FlutterSecureStorage _storage;
ListAPIService(Client client, FlutterSecureStorage storage)
: _storage = storage,
super(client);
@override
Future<TaskList?> create(namespaceId, TaskList tl) {
tl.namespaceId = namespaceId;
return client
.put('/namespaces/$namespaceId/lists', body: tl.toJSON())
.then((response) {
if (response == null) return null;
return TaskList.fromJson(response.body);
});
}
@override
Future delete(int listId) {
return client.delete('/lists/$listId').then((_) {});
}
@override
Future<TaskList?> get(int listId) {
return client.get('/lists/$listId').then((response) {
if (response == null) return null;
final map = response.body;
if (map.containsKey('id')) {
return client.get("/lists/$listId/tasks").then((tasks) {
map['tasks'] = tasks?.body;
return TaskList.fromJson(map);
});
}
return TaskList.fromJson(map);
});
}
@override
Future<List<TaskList>?> getAll() {
return client.get('/lists').then((list) {
if (list == null || list.statusCode != 200) return null;
if (list.body.toString().isEmpty) return Future.value([]);
print(list.statusCode);
return convertList(list.body, (result) => TaskList.fromJson(result));
});
}
@override
Future<List<TaskList>?> getByNamespace(int namespaceId) {
// TODO there needs to be a better way for this. /namespaces/-2/lists should
// return favorite lists
if (namespaceId == -2) {
// Favourites.
return getAll().then((value) {
if (value == null) return null;
value.removeWhere((element) => !element.isFavorite);
return value;
});
}
return client.get('/namespaces/$namespaceId/lists').then((list) {
if (list == null || list.statusCode != 200) return null;
return convertList(list.body, (result) => TaskList.fromJson(result));
});
}
@override
Future<TaskList?> update(TaskList tl) {
return client.post('/lists/${tl.id}', body: tl.toJSON()).then((response) {
if (response == null) return null;
return TaskList.fromJson(response.body);
});
}
@override
Future<String> getDisplayDoneTasks(int listId) {
return _storage.read(key: "display_done_tasks_list_$listId").then((value) {
if (value == null) {
// TODO: implement default value
setDisplayDoneTasks(listId, "1");
return Future.value("1");
}
return value;
});
}
@override
void setDisplayDoneTasks(int listId, String value) {
_storage.write(key: "display_done_tasks_list_$listId", value: value);
}
@override
Future<String?> getDefaultList() {
return _storage.read(key: "default_list_id");
}
@override
void setDefaultList(int? listId) {
_storage.write(key: "default_list_id", value: listId.toString());
}
}

View File

@ -1,49 +0,0 @@
import 'dart:async';
import 'dart:developer';
import 'package:vikunja_app/api/client.dart';
import 'package:vikunja_app/api/service.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/service/services.dart';
class NamespaceAPIService extends APIService implements NamespaceService {
NamespaceAPIService(Client client) : super(client);
@override
Future<Namespace?> create(Namespace ns) {
return client.put('/namespaces', body: ns.toJSON()).then((response) {
if (response == null) return null;
return Namespace.fromJson(response.body);
});
}
@override
Future delete(int namespaceId) {
return client.delete('/namespaces/$namespaceId');
}
@override
Future<Namespace?> get(int namespaceId) {
return client.get('/namespaces/$namespaceId').then((response) {
if (response == null) return null;
return Namespace.fromJson(response.body);
});
}
@override
Future<List<Namespace>?> getAll() {
return client.get('/namespaces').then((response) {
if (response == null) return null;
return convertList(response.body, (result) => Namespace.fromJson(result));
});
}
@override
Future<Namespace?> update(Namespace ns) {
return client
.post('/namespaces/${ns.id}', body: ns.toJSON())
.then((response) {
if (response == null) return null;
return Namespace.fromJson(response.body);
});
}
}

View File

@ -5,7 +5,7 @@ import 'package:flutter/scheduler.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:provider/provider.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/pages/list/task_edit.dart';
import 'package:vikunja_app/pages/project/task_edit.dart';
import 'package:vikunja_app/utils/misc.dart';
import 'package:vikunja_app/theme/constants.dart';

View File

@ -7,9 +7,8 @@ import 'package:provider/provider.dart';
import '../global.dart';
import '../models/bucket.dart';
import '../models/list.dart';
import '../models/project.dart';
import '../pages/list/list.dart';
import '../pages/project/project_task_list.dart';
import '../stores/project_store.dart';
import '../utils/calculate_item_position.dart';
import 'AddDialog.dart';

View File

@ -7,7 +7,7 @@ import 'package:vikunja_app/utils/priority.dart';
import '../models/label.dart';
import '../models/task.dart';
import '../pages/list/task_edit.dart';
import '../pages/project/task_edit.dart';
import '../stores/project_store.dart';
import '../theme/constants.dart';
import 'label.dart';

View File

@ -6,7 +6,7 @@ import 'package:provider/provider.dart';
import 'package:vikunja_app/components/TaskBottomSheet.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/utils/misc.dart';
import 'package:vikunja_app/pages/list/task_edit.dart';
import 'package:vikunja_app/pages/project/task_edit.dart';
import 'package:vikunja_app/utils/priority.dart';
import '../stores/project_store.dart';

View File

@ -1,5 +1,6 @@
import 'dart:developer' as dev;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:vikunja_app/api/bucket_implementation.dart';
@ -7,8 +8,6 @@ import 'package:vikunja_app/api/client.dart';
import 'package:vikunja_app/api/label_task.dart';
import 'package:vikunja_app/api/label_task_bulk.dart';
import 'package:vikunja_app/api/labels.dart';
import 'package:vikunja_app/api/list_implementation.dart';
import 'package:vikunja_app/api/namespace_implementation.dart';
import 'package:vikunja_app/api/server_implementation.dart';
import 'package:vikunja_app/api/task_implementation.dart';
import 'package:vikunja_app/api/user_implementation.dart';
@ -65,16 +64,12 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
VersionChecker get versionChecker => new VersionChecker(snackbarKey);
NamespaceService get namespaceService => new NamespaceAPIService(client);
ProjectService get projectService => new ProjectAPIService(client, _storage);
TaskService get taskService => new TaskAPIService(client);
BucketService get bucketService => new BucketAPIService(client);
ListService get listService => new ListAPIService(client, _storage);
TaskServiceOptions get taskServiceOptions => new TaskServiceOptions();
NotificationClass get notifications => _notificationClass;
@ -89,6 +84,9 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
late String currentTimeZone;
void updateWorkmanagerDuration() {
if (kIsWeb) {
return;
}
Workmanager().cancelAll().then((value) {
settingsManager.getWorkmanagerDuration().then((duration) {
if (duration.inMinutes > 0) {
@ -116,7 +114,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
_client = Client(snackbarKey);
settingsManager
.getIgnoreCertificates()
.then((value) => client.reload_ignore_certs(value == "1"));
.then((value) => client.reloadIgnoreCerts(value == "1"));
_newUserService = UserAPIService(client);
_loadCurrentUser();
tz.initializeTimeZones();

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:permission_handler/permission_handler.dart';
@ -34,6 +35,9 @@ class IgnoreCertHttpOverrides extends HttpOverrides {
@pragma('vm:entry-point')
void callbackDispatcher() {
if (kIsWeb) {
return;
}
Workmanager().executeTask((task, inputData) async {
print(
"Native called background task: $task"); //simpleTask will be emitted here.
@ -48,7 +52,7 @@ void callbackDispatcher() {
.getIgnoreCertificates()
.then((value) async {
print("ignoring: $value");
client.reload_ignore_certs(value == "1");
client.reloadIgnoreCerts(value == "1");
TaskAPIService taskService = TaskAPIService(client);
NotificationClass nc = NotificationClass();
@ -95,18 +99,31 @@ void main() async {
Permission.notification.request();
}
});
await FlutterDownloader.initialize();
Workmanager().initialize(callbackDispatcher, isInDebugMode: false);
try {
if (!kIsWeb) {
await FlutterDownloader.initialize();
}
} catch (e) {
print("Failed to initialize downloader: $e");
}
try {
if (!kIsWeb) {
Workmanager().initialize(callbackDispatcher, isInDebugMode: false);
}
} catch (e) {
print("Failed to initialize workmanager: $e");
}
runApp(VikunjaGlobal(
child: new VikunjaApp(
home: HomePage(),
key: UniqueKey(),
navkey: globalNavigatorKey,
),
login: new VikunjaApp(
home: LoginPage(),
key: UniqueKey(),
)));
child: new VikunjaApp(
home: HomePage(),
key: UniqueKey(),
navkey: globalNavigatorKey,
),
login: new VikunjaApp(
home: LoginPage(),
key: UniqueKey(),
),
));
}
final ValueNotifier<bool> updateTheme = ValueNotifier(false);
@ -118,38 +135,41 @@ class VikunjaApp extends StatelessWidget {
const VikunjaApp({Key? key, required this.home, this.navkey})
: super(key: key);
Future<ThemeData> getThemedata() async {
FlutterThemeMode themeMode = FlutterThemeMode.light;
try {
SettingsManager manager = SettingsManager(new FlutterSecureStorage());
themeMode = await manager.getThemeMode();
} catch (e) {
print("Failed to get theme mode: $e");
}
switch (themeMode) {
case FlutterThemeMode.dark:
return buildVikunjaDarkTheme();
case FlutterThemeMode.materialYouLight:
return buildVikunjaMaterialLightTheme();
case FlutterThemeMode.materialYouDark:
return buildVikunjaMaterialDarkTheme();
default:
return buildVikunjaTheme();
}
}
@override
Widget build(BuildContext context) {
SettingsManager manager = SettingsManager(new FlutterSecureStorage());
return new ValueListenableBuilder(
valueListenable: updateTheme,
builder: (_, mode, __) {
updateTheme.value = false;
FlutterThemeMode themeMode = FlutterThemeMode.system;
Future<ThemeData> theme = manager.getThemeMode().then((value) {
themeMode = value;
switch (value) {
case FlutterThemeMode.dark:
return buildVikunjaDarkTheme();
case FlutterThemeMode.materialYouLight:
return buildVikunjaMaterialLightTheme();
case FlutterThemeMode.materialYouDark:
return buildVikunjaMaterialDarkTheme();
default:
return buildVikunjaTheme();
}
});
return FutureBuilder<ThemeData>(
future: theme,
future: getThemedata(),
builder: (BuildContext context, AsyncSnapshot<ThemeData> data) {
if (data.hasData) {
return new DynamicColorBuilder(
builder: (lightTheme, darkTheme) {
ThemeData? themeData = data.data;
if (themeMode == FlutterThemeMode.materialYouLight)
if (data.data == FlutterThemeMode.materialYouLight)
themeData = themeData?.copyWith(colorScheme: lightTheme);
else if (themeMode == FlutterThemeMode.materialYouDark)
else if (data.data == FlutterThemeMode.materialYouDark)
themeData = themeData?.copyWith(colorScheme: darkTheme);
return MaterialApp(
title: 'Vikunja',

View File

@ -33,7 +33,7 @@ class NotificationClass {
channelDescription: "description",
icon: 'vikunja_notification_logo',
importance: notifs.Importance.high);
late notifs.IOSNotificationDetails iOSSpecifics;
late notifs.DarwinNotificationDetails iOSSpecifics;
late notifs.NotificationDetails platformChannelSpecificsDueDate;
late notifs.NotificationDetails platformChannelSpecificsReminders;
@ -48,7 +48,7 @@ class NotificationClass {
Future<void> _initNotifications() async {
var initializationSettingsAndroid =
notifs.AndroidInitializationSettings('vikunja_logo');
var initializationSettingsIOS = notifs.IOSInitializationSettings(
var initializationSettingsIOS = notifs.DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
@ -60,17 +60,18 @@ class NotificationClass {
var initializationSettings = notifs.InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
await notificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String? payload) async {
onDidReceiveNotificationResponse:
(notifs.NotificationResponse resp) async {
if (payload != null) {
print('notification payload: ' + payload);
selectNotificationSubject.add(payload);
print('notification payload: ' + resp.payload!);
selectNotificationSubject.add(resp.payload!);
}
});
print("Notifications initialised successfully");
}
Future<void> notificationInitializer() async {
iOSSpecifics = notifs.IOSNotificationDetails();
iOSSpecifics = notifs.DarwinNotificationDetails();
platformChannelSpecificsDueDate = notifs.NotificationDetails(
android: androidSpecificsDueDate, iOS: iOSSpecifics);
platformChannelSpecificsReminders = notifs.NotificationDetails(

View File

@ -1,53 +0,0 @@
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/models/user.dart';
class TaskList {
final int id;
int namespaceId;
String title, description;
final User owner;
final DateTime created, updated;
final List<Task> tasks;
final bool isFavorite;
TaskList({
this.id = 0,
required this.title,
required this.namespaceId,
this.description = '',
required this.owner,
DateTime? created,
DateTime? updated,
List<Task>? tasks,
this.isFavorite = false,
}) : this.created = created ?? DateTime.now(),
this.updated = updated ?? DateTime.now(),
this.tasks = 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']),
isFavorite = json['is_favorite'],
namespaceId = json['namespace_id'],
tasks = json['tasks'] == null
? []
: (json['tasks'] as List<dynamic>)
.map((taskJson) => Task.fromJson(taskJson))
.toList();
toJSON() {
return {
'id': id,
'title': title,
'description': description,
'owner': owner.toJSON(),
'created': created.toUtc().toIso8601String(),
'updated': updated.toUtc().toIso8601String(),
'namespace_id': namespaceId
};
}
}

View File

@ -1,53 +0,0 @@
import 'package:vikunja_app/models/user.dart';
class Namespace {
final int id;
final DateTime created, updated;
final String title, description;
final User? owner;
Namespace({
this.id = 0,
DateTime? created,
DateTime? updated,
required this.title,
this.description = '',
required this.owner,
}) : this.created = created ?? DateTime.now(),
this.updated = updated ?? DateTime.now();
Namespace.fromJson(Map<String, dynamic> json)
: title = json['title'],
description = json['description'],
id = json['id'],
created = DateTime.parse(json['created']),
updated = DateTime.parse(json['updated']),
owner = json['owner'] != null ? User.fromJson(json['owner']) : null;
Map<String, dynamic> toJSON() => {
'id': id,
'created': created.toUtc().toIso8601String(),
'updated': updated.toUtc().toIso8601String(),
'title': title,
'owner': owner?.toJSON(),
'description': description
};
Namespace copyWith({
int? id,
DateTime? created,
DateTime? updated,
String? title,
String? description,
User? owner,
}) {
return Namespace(
id: id ?? this.id,
created: created ?? this.created,
updated: updated ?? this.updated,
title: title ?? this.title,
description: description ?? this.description,
owner: owner ?? this.owner,
);
}
}

View File

@ -2,18 +2,10 @@ import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:after_layout/after_layout.dart';
import 'package:provider/provider.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/ErrorDialog.dart';
import 'package:vikunja_app/models/project.dart';
import 'package:vikunja_app/pages/namespace/namespace.dart';
import 'package:vikunja_app/pages/namespace/namespace_edit.dart';
import 'package:vikunja_app/pages/landing_page.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/pages/namespace/overview.dart';
import 'package:vikunja_app/pages/project/overview.dart';
import 'package:vikunja_app/pages/settings.dart';

View File

@ -1,324 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/KanbanWidget.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/models/bucket.dart';
import 'package:vikunja_app/pages/list/list_edit.dart';
import 'package:vikunja_app/pages/list/task_edit.dart';
import 'package:vikunja_app/stores/list_store.dart';
import '../../components/pagestatus.dart';
enum BucketMenu { limit, done, delete }
class BucketProps {
final ScrollController controller = ScrollController();
final TextEditingController titleController = TextEditingController();
bool scrollable = false;
bool portrait = true;
int bucketLength = 0;
Size? taskDropSize;
}
class ListPage extends StatefulWidget {
final TaskList taskList;
//ListPage({this.taskList}) : super(key: Key(taskList.id.toString()));
ListPage({required this.taskList})
: super(key: Key(Random().nextInt(100000).toString()));
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
final _keyboardController = KeyboardVisibilityController();
int _viewIndex = 0;
late TaskList _list;
List<Task> _loadingTasks = [];
int _currentPage = 1;
bool displayDoneTasks = false;
late ListProvider taskState;
late KanbanClass _kanban;
@override
void initState() {
_list = widget.taskList;
_keyboardController.onChange.listen((visible) {
if (!visible && mounted) FocusScope.of(context).unfocus();
});
super.initState();
}
void nullSetState() {
setState(() {});
}
@override
Widget build(BuildContext context) {
taskState = Provider.of<ListProvider>(context);
//_kanban = KanbanClass(
// context, nullSetState, _onViewTapped, _addItemDialog, _list);
Widget body;
switch (taskState.pageStatus) {
case PageStatus.built:
Future.delayed(Duration.zero, _loadList);
body = new Stack(children: [
ListView(),
Center(
child: CircularProgressIndicator(),
)
]);
break;
case PageStatus.loading:
body = new Stack(children: [
ListView(),
Center(
child: CircularProgressIndicator(),
)
]);
break;
case PageStatus.error:
body = new Stack(children: [
ListView(),
Center(child: Text("There was an error loading this view"))
]);
break;
case PageStatus.success:
body = taskState.tasks.length > 0 || taskState.buckets.length > 0
? ListenableProvider.value(
value: taskState,
child: Theme(
data: (ThemeData base) {
return base.copyWith(
chipTheme: base.chipTheme.copyWith(
labelPadding: EdgeInsets.symmetric(horizontal: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(5)),
),
),
);
}(Theme.of(context)),
child: () {
switch (_viewIndex) {
case 0:
return _listView(context);
case 1:
return _kanban.kanbanView();
default:
return _listView(context);
}
}(),
),
)
: Stack(children: [
ListView(),
Center(child: Text('This list is empty.'))
]);
break;
case PageStatus.empty:
body = new Stack(
children: [ListView(), Center(child: Text("This view is empty"))]);
break;
}
return new Scaffold(
appBar: AppBar(
title: Text(_list.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListEditPage(
list: _list,
),
)).whenComplete(() => _loadList()),
),
],
),
body: RefreshIndicator(onRefresh: () => _loadList(), child: body),
floatingActionButton: _viewIndex == 1
? null
: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => _addItemDialog(context),
child: Icon(Icons.add)),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.view_list),
label: 'List',
tooltip: 'List',
),
BottomNavigationBarItem(
icon: Icon(Icons.view_kanban),
label: 'Kanban',
tooltip: 'Kanban',
),
],
currentIndex: _viewIndex,
onTap: _onViewTapped,
),
);
}
void _onViewTapped(int index) {
_loadList().then((_) {
_currentPage = 1;
setState(() {
_viewIndex = index;
});
});
}
ListView _listView(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0),
itemCount: taskState.tasks.length * 2,
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
if (_loadingTasks.isNotEmpty) {
final loadingTask = _loadingTasks.removeLast();
return _buildLoadingTile(loadingTask);
}
final index = i ~/ 2;
if (taskState.maxPages == _currentPage &&
index == taskState.tasks.length)
throw Exception("Check itemCount attribute");
if (index >= taskState.tasks.length &&
_currentPage < taskState.maxPages) {
_currentPage++;
_loadTasksForPage(_currentPage);
}
return _buildTile(taskState.tasks[index]);
});
}
Widget _buildTile(Task task) {
return ListenableProvider.value(
value: taskState,
child: TaskTile(
task: task,
loading: false,
onEdit: () {},
onMarkedAsDone: (done) {
Provider.of<ListProvider>(context, listen: false).updateTask(
context: context,
task: task.copyWith(done: done),
);
},
),
);
}
Future<void> updateDisplayDoneTasks() {
return VikunjaGlobal.of(context)
.listService
.getDisplayDoneTasks(_list.id)
.then((value) {
displayDoneTasks = value == "1";
});
}
TaskTile _buildLoadingTile(Task task) {
return TaskTile(
task: task,
loading: true,
onEdit: () {},
);
}
Future<void> _loadList() async {
taskState.pageStatus = (PageStatus.loading);
updateDisplayDoneTasks().then((value) async {
switch (_viewIndex) {
case 0:
_loadTasksForPage(1);
break;
case 1:
await _kanban.loadBucketsForPage(1);
// load all buckets to get length for RecordableListView
while (_currentPage < taskState.maxPages) {
_currentPage++;
await _kanban.loadBucketsForPage(_currentPage);
}
break;
default:
_loadTasksForPage(1);
}
});
}
Future<void> _loadTasksForPage(int page) {
return Provider.of<ListProvider>(context, listen: false).loadTasks(
context: context,
listId: _list.id,
page: page,
displayDoneTasks: displayDoneTasks);
}
Future<void> _addItemDialog(BuildContext context, [Bucket? bucket]) {
return showDialog(
context: context,
builder: (_) => AddDialog(
onAdd: (title) => _addItem(title, context, bucket),
decoration: InputDecoration(
labelText:
(bucket != null ? '\'${bucket.title}\': ' : '') + 'New Task Name',
hintText: 'eg. Milk',
),
),
);
}
Future<void> _addItem(String title, BuildContext context,
[Bucket? bucket]) async {
final currentUser = VikunjaGlobal.of(context).currentUser;
if (currentUser == null) {
return;
}
final newTask = Task(
title: title,
createdBy: currentUser,
done: false,
bucketId: bucket?.id,
projectId: _list.id,
);
setState(() => _loadingTasks.add(newTask));
return Provider.of<ListProvider>(context, listen: false)
.addTask(
context: context,
newTask: newTask,
listId: _list.id,
)
.then((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('The task was added successfully' +
(bucket != null ? ' to \'${bucket.title}\'' : '') +
'!'),
));
setState(() {
_loadingTasks.remove(newTask);
});
});
}
}

View File

@ -1,174 +0,0 @@
import 'dart:ffi';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.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({required 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 = '';
bool? displayDoneTasks;
late int listId;
@override
void initState() {
listId = widget.list.id;
super.initState();
}
@override
Widget build(BuildContext ctx) {
if (displayDoneTasks == null)
VikunjaGlobal.of(context)
.listService
.getDisplayDoneTasks(listId)
.then((value) => setState(() => displayDoneTasks = value == "1"));
else
log("Display done tasks: " + displayDoneTasks.toString());
return Scaffold(
appBar: AppBar(
title: Text('Edit List'),
),
body: Builder(
builder: (BuildContext context) => SafeArea(
child: Form(
key: _formKey,
child: ListView(
//reverse: true,
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 == null) return null;
if (description.length > 1000) {
return 'The description can have a maximum of 1000 characters.';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(),
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: CheckboxListTile(
value: displayDoneTasks ?? false,
title: Text("Show done tasks"),
onChanged: (value) {
value ??= false;
VikunjaGlobal.of(context)
.listService
.setDisplayDoneTasks(listId, value ? "1" : "0");
setState(() => displayDoneTasks = value);
},
),
),
Builder(
builder: (context) => Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: FancyButton(
onPressed: !_loading
? () {
if (_formKey.currentState!.validate()) {
Form.of(context)?.save();
_saveList(context);
}
}
: () {},
child: _loading
? CircularProgressIndicator()
: VikunjaButtonText('Save'),
))),
/*ExpansionTile(
title: Text("Sharing"),
children: [
TypeAheadFormField(
onSuggestionSelected: (suggestion) {},
itemBuilder: (BuildContext context, Object? itemData) {
return Card(
child: Container(
padding: EdgeInsets.all(10),
child: Text(itemData.toString())),
);},
suggestionsCallback: (String pattern) {
List<String> matches = <String>[];
matches.addAll(["test", "test2", "test3"]);
matches.retainWhere((s){
return s.toLowerCase().contains(pattern.toLowerCase());
});
return matches;
},)
],
)*/
]),
),
),
),
);
}
_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?)
widget.list.title = _title;
widget.list.description = _description;
VikunjaGlobal.of(context).listService.update(widget.list).then((_) {
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('The list was updated successfully!'),
));
}).catchError((err) {
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Something went wrong: ' + err.toString()),
action: SnackBarAction(
label: 'CLOSE',
onPressed: ScaffoldMessenger.of(context).hideCurrentSnackBar),
),
);
});
}
}

View File

@ -1,192 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.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';
import 'package:vikunja_app/pages/namespace/namespace_edit.dart';
import 'package:vikunja_app/stores/list_store.dart';
import '../../components/pagestatus.dart';
class NamespacePage extends StatefulWidget {
final Namespace namespace;
NamespacePage({required this.namespace})
: super(key: Key(namespace.id.toString()));
@override
_NamespacePageState createState() => new _NamespacePageState();
}
class _NamespacePageState extends State<NamespacePage> {
List<TaskList> _lists = [];
PageStatus namespacestatus = PageStatus.loading;
/////
// This essentially shows the lists.
@override
Widget build(BuildContext context) {
Widget body;
switch (namespacestatus) {
case PageStatus.built:
_loadLists();
body = new Stack(children: [
ListView(),
Center(
child: CircularProgressIndicator(),
)
]);
break;
case PageStatus.loading:
body = new Stack(children: [
ListView(),
Center(
child: CircularProgressIndicator(),
)
]);
break;
case PageStatus.error:
body = new Stack(children: [
ListView(),
Center(child: Text("There was an error loading this view"))
]);
break;
case PageStatus.success:
body = 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((_) => ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(content: Text("${ls.title} removed"))));
},
))).toList(),
);
break;
case PageStatus.empty:
body = new Stack(
children: [ListView(), Center(child: Text("This view is empty"))]);
break;
}
return new Scaffold(
appBar: AppBar(
title: Text(widget.namespace.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NamespaceEditPage(
namespace: widget.namespace,
),
)).whenComplete(() => _loadLists()),
),
],
),
body: RefreshIndicator(onRefresh: () => _loadLists(), child: body),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => _addListDialog(context),
child: const Icon(Icons.add))),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_loadLists();
}
Future<void> _removeList(TaskList list) {
return VikunjaGlobal.of(context)
.listService
.delete(list.id)
.then((_) => _loadLists());
}
Future<void> _loadLists() {
// FIXME: This is called even when the tasks on a list are loaded - which is not needed at all
namespacestatus = PageStatus.loading;
return VikunjaGlobal.of(context)
.listService
.getByNamespace(widget.namespace.id)
.then((lists) => setState(() {
if (lists != null) {
this._lists = lists;
namespacestatus = PageStatus.success;
} else {
namespacestatus = PageStatus.error;
}
}));
}
_openList(BuildContext context, TaskList list) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ChangeNotifierProvider<ListProvider>(
create: (_) => new ListProvider(),
child: ListPage(
taskList: list,
),
),
// 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')),
);
}
void _addList(String name, BuildContext context) {
final curentUser = VikunjaGlobal.of(context).currentUser;
if (curentUser == null) {
return;
}
VikunjaGlobal.of(context)
.listService
.create(
widget.namespace.id,
TaskList(
title: name,
tasks: [],
namespaceId: widget.namespace.id,
owner: curentUser,
))
.then((_) {
setState(() {});
_loadLists();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('The list was successfully created!'),
),
);
});
}
}

View File

@ -1,131 +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({required this.namespace})
: super(key: Key(namespace.toString()));
@override
State<StatefulWidget> createState() => _NamespaceEditPageState();
}
class _NamespaceEditPageState extends State<NamespaceEditPage> {
final _formKey = GlobalKey<FormState>();
bool _loading = false;
late String _name, _description;
@override
void initState() {
_name = widget.namespace.title;
_description = widget.namespace.description;
super.initState();
}
@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);
final updatedNamespace = widget.namespace.copyWith(
title: _name,
description: _description,
);
VikunjaGlobal.of(context)
.namespaceService
.update(updatedNamespace)
.then((_) {
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('The namespace was updated successfully!'),
));
}).catchError((err) {
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Something went wrong: ' + err.toString()),
action: SnackBarAction(
label: 'CLOSE',
onPressed: ScaffoldMessenger.of(context).hideCurrentSnackBar),
),
);
});
}
}

View File

@ -1,124 +0,0 @@
import 'package:after_layout/after_layout.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../components/AddDialog.dart';
import '../../components/ErrorDialog.dart';
import '../../global.dart';
import '../../models/namespace.dart';
import 'namespace.dart';
class NamespaceOverviewPage extends StatefulWidget {
@override
_NamespaceOverviewPageState createState() =>
new _NamespaceOverviewPageState();
}
class _NamespaceOverviewPageState extends State<NamespaceOverviewPage>
with AfterLayoutMixin<NamespaceOverviewPage> {
List<Namespace> _namespaces = [];
int _selectedDrawerIndex = -2, _previousDrawerIndex = -2;
bool _loading = true;
Namespace? get _currentNamespace =>
_selectedDrawerIndex >= -1 && _selectedDrawerIndex < _namespaces.length
? _namespaces[_selectedDrawerIndex]
: null;
@override
void afterFirstLayout(BuildContext context) {
_loadNamespaces();
}
@override
Widget build(BuildContext context) {
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),
)));
if (_selectedDrawerIndex > -1) {
return new WillPopScope(
child: NamespacePage(namespace: _namespaces[_selectedDrawerIndex]),
onWillPop: () async {
setState(() {
_selectedDrawerIndex = -2;
});
return false;
});
}
return Scaffold(
body: this._loading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(
context: context, tiles: namespacesList)
.toList()),
onRefresh: _loadNamespaces,
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => _addNamespaceDialog(context),
child: const Icon(Icons.add))),
appBar: AppBar(
title: Text("Namespaces"),
),
);
}
Future<void> _loadNamespaces() {
return VikunjaGlobal.of(context).namespaceService.getAll().then((result) {
setState(() {
_loading = false;
if (result != null) _namespaces = result;
});
});
}
_onSelectItem(int index) {
Navigator.push(
context,
MaterialPageRoute(
builder: (buildContext) => NamespacePage(
namespace: _namespaces[index],
),
));
//setState(() => _selectedDrawerIndex = index);
}
_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) {
final currentUser = VikunjaGlobal.of(context).currentUser;
if (currentUser == null) {
return;
}
VikunjaGlobal.of(context)
.namespaceService
.create(Namespace(title: name, owner: currentUser))
.then((_) {
_loadNamespaces();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('The namespace was created successfully!'),
));
}).catchError((error) => showDialog(
context: context, builder: (context) => ErrorDialog(error: error)));
}
}

View File

@ -1,4 +1,3 @@
import 'dart:ffi';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
@ -99,7 +98,7 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
onChanged: (value) {
value ??= false;
VikunjaGlobal.of(context)
.listService
.projectService
.setDisplayDoneTasks(listId, value ? "1" : "0");
setState(() => displayDoneTasks = value);
},

View File

@ -8,12 +8,10 @@ import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/KanbanWidget.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/models/bucket.dart';
import 'package:vikunja_app/pages/list/list_edit.dart';
import 'package:vikunja_app/pages/list/task_edit.dart';
import 'package:vikunja_app/pages/project/project_edit.dart';
import 'package:vikunja_app/pages/project/task_edit.dart';
import '../../components/pagestatus.dart';
import '../../models/project.dart';

View File

@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/list.dart';
import 'package:collection/collection.dart';
import '../main.dart';
@ -173,7 +172,7 @@ class SettingsPageState extends State<SettingsPage> {
value: ignoreCertificates,
onChanged: (value) {
setState(() => ignoreCertificates = value);
VikunjaGlobal.of(context).client.reload_ignore_certs(value);
VikunjaGlobal.of(context).client.reloadIgnoreCerts(value);
})
: ListTile(title: Text("...")),
Divider(),

View File

@ -264,7 +264,7 @@ class _LoginPageState extends State<LoginPage> {
value: client.ignoreCertificates,
onChanged: (value) {
setState(
() => client.reload_ignore_certs(value ?? false));
() => client.reloadIgnoreCerts(value ?? false));
VikunjaGlobal.of(context)
.settingsManager
.setIgnoreCertificates(value ?? false);

View File

@ -1,202 +1,11 @@
import 'dart:async';
import 'package:vikunja_app/api/response.dart';
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';
import 'package:vikunja_app/service/services.dart';
// Data for mocked services
var _users = {1: User(id: 1, username: 'test1')};
var _namespaces = {
1: Namespace(
id: 1,
title: 'Test 1',
created: DateTime.now(),
updated: DateTime.now(),
description: 'A namespace for testing purposes',
owner: _users[1]!,
)
};
var _nsLists = {
1: [1]
};
var _lists = {
1: TaskList(
id: 1,
title: 'List 1',
tasks: _tasks.values.toList(),
owner: _users[1]!,
description: 'A nice list',
created: DateTime.now(),
updated: DateTime.now(),
namespaceId: 1)
};
var _tasks = {
1: Task(
id: 1,
title: 'Task 1',
createdBy: _users[1]!,
updated: DateTime.now(),
created: DateTime.now(),
description: 'A descriptive task',
done: false,
projectId: 1,
)
};
// Mocked services
class MockedNamespaceService implements NamespaceService {
@override
Future<Namespace> create(Namespace ns) {
_namespaces[ns.id] = ns;
return Future.value(ns);
}
@override
Future delete(int namespaceId) {
_namespaces.remove(namespaceId);
return Future.value();
}
@override
Future<Namespace> get(int namespaceId) {
return Future.value(_namespaces[namespaceId]);
}
@override
Future<List<Namespace>> getAll() {
return Future.value(_namespaces.values.toList());
}
@override
Future<Namespace> update(Namespace ns) {
if (!_namespaces.containsKey(ns.id))
throw Exception('Namespace ${ns.id} does not exsists');
return create(ns);
}
}
class MockedListService implements ListService {
@override
Future<TaskList> create(namespaceId, TaskList tl) {
_nsLists[namespaceId]?.add(tl.id);
return Future.value(_lists[tl.id] = tl);
}
@override
Future delete(int listId) {
_lists.remove(listId);
return Future.value();
}
@override
Future<TaskList> get(int listId) {
return Future.value(_lists[listId]);
}
@override
Future<List<TaskList>> getAll() {
return Future.value(_lists.values.toList());
}
@override
Future<List<TaskList>> getByNamespace(int namespaceId) {
return Future.value(
_nsLists[namespaceId]!.map((listId) => _lists[listId]!).toList());
}
@override
Future<TaskList> update(TaskList tl) {
if (!_lists.containsKey(tl))
throw Exception('TaskList ${tl.id} does not exists');
return Future.value(_lists[tl.id] = tl);
}
@override
Future<String> getDisplayDoneTasks(int listId) {
// TODO: implement getDisplayDoneTasks
throw UnimplementedError();
}
@override
void setDisplayDoneTasks(int listId, String value) {
// TODO: implement setDisplayDoneTasks
}
@override
Future<String> getDefaultList() {
// TODO: implement getDefaultList
throw UnimplementedError();
}
@override
void setDefaultList(int? listId) {
// TODO: implement setDefaultList
}
}
class MockedTaskService implements TaskService {
@override
Future delete(int taskId) {
_lists.forEach(
(_, list) => list.tasks.removeWhere((task) => task.id == taskId));
_tasks.remove(taskId);
return Future.value();
}
@override
Future<Task> update(Task task) {
_lists.forEach((_, list) {
if (list.tasks.where((t) => t.id == task.id).length > 0) {
list.tasks.removeWhere((t) => t.id == task.id);
list.tasks.add(task);
}
});
return Future.value(_tasks[task.id] = task);
}
@override
Future<Task> add(int listId, Task task) {
var id = _tasks.keys.last + 1;
_tasks[id] = task;
_lists[listId]!.tasks.add(task);
return Future.value(task);
}
@override
int get maxPages => 1;
Future<Task> get(int taskId) {
// TODO: implement get
throw UnimplementedError();
}
@override
Future<List<Task>> getByOptions(TaskServiceOptions options) {
// TODO: implement getByOptions
throw UnimplementedError();
}
@override
Future<List<Task>> getAll() {
// TODO: implement getAll
throw UnimplementedError();
}
@override
Future<Response?> getAllByProject(int projectId,
[Map<String, List<String>>? queryParameters]) {
// TODO: implement getAllByProject
return Future.value(new Response(_tasks.values.toList(), 200, {}));
}
}
class MockedUserService implements UserService {
@override
Future<UserTokenPair> login(String username, password,

View File

@ -1,13 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:vikunja_app/api/response.dart';
import 'package:vikunja_app/models/label.dart';
import 'package:vikunja_app/models/labelTask.dart';
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';
import 'package:vikunja_app/models/bucket.dart';
@ -152,40 +149,6 @@ abstract class ProjectService {
//void setDefaultList(int? listId);
}
abstract class NamespaceService {
Future<List<Namespace>?> getAll();
Future<Namespace?> get(int namespaceId);
Future<Namespace?> create(Namespace ns);
Future<Namespace?> update(Namespace ns);
Future delete(int namespaceId);
}
abstract class ListService {
Future<List<TaskList>?> getAll();
Future<TaskList?> get(int listId);
Future<List<TaskList>?> getByNamespace(int namespaceId);
Future<TaskList?> create(int namespaceId, TaskList tl);
Future<TaskList?> update(TaskList tl);
Future delete(int listId);
Future<String?> getDisplayDoneTasks(int listId);
void setDisplayDoneTasks(int listId, String value);
Future<String?> getDefaultList();
//void setDefaultList(int? listId);
}
abstract class TaskService {
Future<Task?> get(int taskId);
@ -324,10 +287,10 @@ class SettingsManager {
key: "workmanager-duration", value: duration.inMinutes.toString());
}
Future<List<String>?> getPastServers() {
return _storage
.read(key: "recent-servers")
.then((value) => (jsonDecode(value!) as List<dynamic>).cast<String>());
Future<List<String>?> getPastServers() async {
String jsonString = await _storage.read(key: "recent-servers") ?? "[]";
List<dynamic> server = jsonDecode(jsonString);
return server.map((e) => e as String).toList();
}
Future<void> setPastServers(List<String>? server) {

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev"
source: hosted
version: "64.0.0"
version: "67.0.0"
after_layout:
dependency: "direct main"
description:
@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev"
source: hosted
version: "6.2.0"
version: "6.4.1"
archive:
dependency: transitive
description:
@ -122,21 +122,21 @@ packages:
source: hosted
version: "2.0.3"
chewie:
dependency: "direct main"
dependency: transitive
description:
name: chewie
sha256: "745e81e84c6d7f3835f89f85bb49771c0a66099e4caf8f8e9e9a372bc66fb2c1"
sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "1.7.5"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.3.5"
version: "0.4.1"
clock:
dependency: transitive
description:
@ -146,7 +146,7 @@ packages:
source: hosted
version: "1.1.1"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
@ -169,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.7.2"
cronet_http:
dependency: "direct main"
description:
name: cronet_http
sha256: "9b9f00ae48971bc8a8cbdd4528bd35511adce00fb79d1ebf9f9907667056640f"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
crypto:
dependency: transitive
description:
@ -185,6 +193,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
cupertino_http:
dependency: "direct main"
description:
name: cupertino_http
sha256: "20c167fd843c9ff6fc25cc4a0e8efa4180dfe119fb6d18c3c55104113e9cfc6f"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
cupertino_icons:
dependency: "direct main"
description:
@ -197,10 +213,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.6"
datetime_picker_formfield:
dependency: "direct main"
description:
@ -229,10 +245,10 @@ packages:
dependency: "direct main"
description:
name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev"
source: hosted
version: "1.6.9"
version: "1.7.0"
fake_async:
dependency: transitive
description:
@ -245,18 +261,18 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.2"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "7.0.0"
fixnum:
dependency: transitive
description:
@ -298,10 +314,10 @@ packages:
dependency: "direct main"
description:
name: flutter_keyboard_visibility
sha256: "183ef857869a790aaff0219327a31783017fcc54b895fcdda1909645bb6ab965"
sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8"
url: "https://pub.dev"
source: hosted
version: "5.4.2"
version: "6.0.0"
flutter_keyboard_visibility_linux:
dependency: transitive
description:
@ -346,42 +362,42 @@ packages:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: a9de6706cd844668beac27c0aed5910fa0534832b3c2cad61a5fd977fce82a5d
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.10.0"
version: "0.13.1"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64"
sha256: f9a05409385b77b06c18f200a41c7c2711ebf7415669350bb0f8474c07bd40d1
url: "https://pub.dev"
source: hosted
version: "9.9.1"
version: "17.0.0"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "4.0.0+1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a"
sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "7.0.0+1"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f"
sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685
url: "https://pub.dev"
source: hosted
version: "8.1.0"
version: "9.0.0"
flutter_secure_storage_linux:
dependency: transitive
description:
@ -418,18 +434,18 @@ packages:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255"
sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "3.0.0"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev"
source: hosted
version: "2.0.9"
version: "2.0.10+1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -447,10 +463,10 @@ packages:
dependency: "direct main"
description:
name: flutter_typeahead
sha256: "1f6b248bb4f3ebb4cf1ee0354aa23c77be457fb2d26d6847ecc33a917f65e58e"
sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d
url: "https://pub.dev"
source: hosted
version: "5.0.1"
version: "5.2.0"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -548,10 +564,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev"
source: hosted
version: "0.13.6"
version: "1.2.1"
http_multi_server:
dependency: transitive
description:
@ -572,18 +588,18 @@ packages:
dependency: transitive
description:
name: image
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "4.1.7"
intl:
dependency: "direct main"
description:
name: intl
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.17.0"
version: "0.19.0"
io:
dependency: transitive
description:
@ -592,6 +608,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
jni:
dependency: transitive
description:
name: jni
sha256: "499558e919997adfc45809a66caf0b95b91393e23289dd2826b152f8f04e6611"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
js:
dependency: transitive
description:
@ -601,7 +625,7 @@ packages:
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
dependency: "direct main"
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
@ -640,6 +664,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.4.9"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging:
dependency: transitive
description:
@ -652,26 +700,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.8.0"
meta:
dependency: transitive
dependency: "direct main"
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -716,10 +764,10 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745"
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "4.2.0"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -732,10 +780,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_drawing:
dependency: transitive
description:
@ -804,50 +852,58 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
url: "https://pub.dev"
source: hosted
version: "10.4.5"
version: "11.3.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
url: "https://pub.dev"
source: hosted
version: "10.3.6"
version: "12.0.5"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
url: "https://pub.dev"
source: hosted
version: "9.1.4"
version: "9.4.4"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
url: "https://pub.dev"
source: hosted
version: "3.12.0"
version: "4.2.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
version: "0.2.1"
petitparser:
dependency: "direct main"
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "5.4.0"
version: "6.0.2"
platform:
dependency: transitive
description:
@ -868,10 +924,34 @@ packages:
dependency: transitive
description:
name: pointer_interceptor
sha256: adf7a637f97c077041d36801b43be08559fd4322d2127b3f20bb7be1b9eebc22
sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a
url: "https://pub.dev"
source: hosted
version: "0.9.3+7"
version: "0.10.1"
pointer_interceptor_ios:
dependency: transitive
description:
name: pointer_interceptor_ios
sha256: "2e73c39452830adc4695757130676a39412a3b7f3c34e3f752791b5384770877"
url: "https://pub.dev"
source: hosted
version: "0.10.0+2"
pointer_interceptor_platform_interface:
dependency: transitive
description:
name: pointer_interceptor_platform_interface
sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506"
url: "https://pub.dev"
source: hosted
version: "0.10.0+1"
pointer_interceptor_web:
dependency: transitive
description:
name: pointer_interceptor_web
sha256: a6237528b46c411d8d55cdfad8fcb3269fc4cbb26060b14bff94879165887d1e
url: "https://pub.dev"
source: hosted
version: "0.10.2"
pointycastle:
dependency: transitive
description:
@ -888,22 +968,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
provider:
dependency: "direct main"
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.2"
pub_semver:
dependency: transitive
description:
@ -1025,10 +1097,10 @@ packages:
dependency: transitive
description:
name: sqflite_common
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
version: "2.5.4"
stack_trace:
dependency: transitive
description:
@ -1094,13 +1166,13 @@ packages:
source: hosted
version: "0.5.9"
timezone:
dependency: transitive
dependency: "direct main"
description:
name: timezone
sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e"
sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.9.2"
typed_data:
dependency: transitive
description:
@ -1113,26 +1185,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.2.5"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.3.0"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
@ -1153,18 +1225,18 @@ packages:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.3.0"
url_launcher_windows:
dependency: transitive
description:
@ -1185,26 +1257,26 @@ packages:
dependency: transitive
description:
name: vector_graphics
sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752"
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
version: "1.1.11+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
version: "1.1.11+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a"
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
version: "1.1.11+1"
vector_math:
dependency: transitive
description:
@ -1217,18 +1289,18 @@ packages:
dependency: transitive
description:
name: video_player
sha256: fbf28ce8bcfe709ad91b5789166c832cb7a684d14f571a81891858fefb5bb1c2
sha256: afc65f4b8bcb2c188f64a591f84fb471f4f2e19fc607c65fd8d2f8fedb3dec23
url: "https://pub.dev"
source: hosted
version: "2.8.2"
version: "2.8.3"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: "7f8f25d7ad56819a82b2948357f3c3af071f6a678db33833b26ec36bbc221316"
sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2"
url: "https://pub.dev"
source: hosted
version: "2.4.11"
version: "2.4.12"
video_player_avfoundation:
dependency: transitive
description:
@ -1249,10 +1321,10 @@ packages:
dependency: transitive
description:
name: video_player_web
sha256: "34beb3a07d4331a24f7e7b2f75b8e2b103289038e07e65529699a671b6a6e2cb"
sha256: "41245cef5ef29c4585dbabcbcbe9b209e34376642c7576cabf11b4ad9289d6e4"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.3.0"
vm_service:
dependency: transitive
description:
@ -1261,46 +1333,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "13.0.0"
wakelock:
wakelock_plus:
dependency: transitive
description:
name: wakelock
sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db"
name: wakelock_plus
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d
url: "https://pub.dev"
source: hosted
version: "0.6.2"
wakelock_macos:
version: "1.1.4"
wakelock_plus_platform_interface:
dependency: transitive
description:
name: wakelock_macos
sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd"
name: wakelock_plus_platform_interface
sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_platform_interface:
dependency: transitive
description:
name: wakelock_platform_interface
sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_windows:
dependency: transitive
description:
name: wakelock_windows
sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
version: "1.1.0"
watcher:
dependency: transitive
description:
@ -1313,18 +1361,26 @@ packages:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.5.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3f81fde6fbc799d03c0fb3f2c3ac84368ee267012a4beb876685c029946da4e0"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.4"
webkit_inspection_protocol:
dependency: transitive
description:
@ -1337,18 +1393,18 @@ packages:
dependency: "direct main"
description:
name: webview_flutter
sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
url: "https://pub.dev"
source: hosted
version: "4.5.0"
version: "4.7.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0"
sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934
url: "https://pub.dev"
source: hosted
version: "3.15.0"
version: "3.16.0"
webview_flutter_platform_interface:
dependency: transitive
description:
@ -1361,18 +1417,18 @@ packages:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "4d062ad505390ecef1c4bfb6001cd857a51e00912cc9dfb66edb1886a9ebd80c"
sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7
url: "https://pub.dev"
source: hosted
version: "3.10.2"
version: "3.13.0"
win32:
dependency: transitive
description:
name: win32
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "5.3.0"
workmanager:
dependency: "direct main"
description:
@ -1385,18 +1441,18 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "0.2.0+3"
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.5.0"
yaml:
dependency: transitive
description:
@ -1406,5 +1462,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.16.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"

View File

@ -9,40 +9,44 @@ environment:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.5
http: ^0.13.5
cupertino_icons: ^1.0.6
http: ^1.2.1
after_layout: ^1.2.0
intl: ^0.17.0
flutter_local_notifications: ^9.8.0+1
rxdart: ^0.27.5
flutter_timezone: ^1.0.4
flutter_secure_storage: ^8.0.0
intl: ^0.19.0
flutter_local_notifications: ^17.0.0
rxdart: ^0.27.7
flutter_timezone: ^1.0.8
flutter_secure_storage: ^9.0.0
datetime_picker_formfield: ^2.0.1
flutter_typeahead: ^5.0.1
build: ^2.3.0
json_serializable: ^6.3.1
petitparser: ^5.0.0
provider: ^6.0.3
webview_flutter: ^4.4.3
flutter_typeahead: ^5.2.0
build: ^2.4.1
json_serializable: ^6.7.1
petitparser: ^6.0.2
provider: ^6.1.2
webview_flutter: ^4.7.0
flutter_colorpicker: ^1.0.3
flutter_keyboard_visibility: ^5.4.2
dotted_border: ^2.0.0+2
package_info_plus: ^3.0.2
url_launcher: ^6.1.7
workmanager: ^0.5.1
permission_handler: ^10.2.0
dynamic_color: ^1.6.6
chewie: ^1.5.0
flutter_widget_from_html: ^0.14.10
flutter_keyboard_visibility: ^6.0.0
dotted_border: ^2.1.0
url_launcher: ^6.2.5
workmanager: ^0.5.2
permission_handler: ^11.3.0
dynamic_color: ^1.7.0
flutter_widget_from_html: ^0.14.11
flutter_downloader: ^1.11.6
meta: ^1.11.0
timezone: ^0.9.2
json_annotation: ^4.8.1
collection: ^1.18.0
cupertino_http: ^1.4.0
cronet_http: ^1.2.0
package_info_plus: any
dev_dependencies:
flutter_test:
sdk: flutter
version: any
test: ^1.21.1
flutter_launcher_icons: ^0.10.0
test: ^1.24.9
flutter_launcher_icons: ^0.13.1
flutter_icons:
image_path: "assets/vikunja_logo.png"

BIN
web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
web/icons/Icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
web/icons/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

59
web/index.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="vikunja_app">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>vikunja_app</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>

35
web/manifest.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "vikunja_app",
"short_name": "vikunja_app",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}