From d4f234d65cc5b759c033e9c42c76e450f0b82d2d Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sat, 3 Sep 2022 10:43:16 -0500 Subject: [PATCH] null-safety & some other cleanup --- .vscode/launch.json | 6 +- lib/api/client.dart | 28 +--- lib/api/labels.dart | 4 +- lib/components/AddDialog.dart | 10 +- lib/components/BucketTaskCard.dart | 43 +++-- lib/components/SliverBucketList.dart | 11 +- lib/components/TaskTile.dart | 25 +-- lib/components/label.dart | 26 +-- lib/global.dart | 56 ++++--- lib/models/bucket.dart | 74 +++++---- lib/models/label.dart | 45 ++--- lib/models/labelTask.dart | 5 +- lib/models/list.dart | 45 ++--- lib/models/namespace.dart | 38 +++-- lib/models/task.dart | 208 ++++++++++++------------ lib/models/taskAttachment.dart | 27 ++- lib/models/user.dart | 33 +++- lib/pages/home.dart | 35 ++-- lib/pages/landing_page.dart | 29 +++- lib/pages/list/list.dart | 77 ++++----- lib/pages/list/list_edit.dart | 41 ++--- lib/pages/list/task_edit.dart | 90 +++++----- lib/pages/namespace/namespace.dart | 21 ++- lib/pages/namespace/namespace_edit.dart | 26 ++- lib/pages/settings.dart | 2 +- lib/pages/user/login.dart | 34 ++-- lib/service/mocked_services.dart | 18 +- lib/stores/list_store.dart | 27 ++- lib/utils/checkboxes_in_text.dart | 4 +- lib/utils/validator.dart | 8 +- test/parse_label_color.dart | 5 +- 31 files changed, 570 insertions(+), 531 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1b2ba13..dc01eb3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,7 +4,11 @@ "name": "Flutter", "request": "launch", "type": "dart", - "flutterMode": "debug" + "flutterMode": "debug", + "args": [ + "--flavor", + "main" + ], } ] } diff --git a/lib/api/client.dart b/lib/api/client.dart index f42682d..8d00ecf 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -16,13 +16,13 @@ class Client { GlobalKey global; final JsonDecoder _decoder = new JsonDecoder(); final JsonEncoder _encoder = new JsonEncoder(); - String? _token; - String? _base; + String _token = ''; + String _base = ''; bool authenticated = false; bool ignoreCertificates = false; - String? get base => _base; - String? get token => _token; + String get base => _base; + String get token => _token; String? post_body; @@ -45,7 +45,7 @@ class Client { get _headers => { - 'Authorization': _token != null ? 'Bearer $_token' : '', + 'Authorization': _token != '' ? 'Bearer $_token' : '', 'Content-Type': 'application/json' }; @@ -63,26 +63,14 @@ class Client { void reset() { - _token = _base = null; + _token = _base = ''; authenticated = false; } Future get(String url, [Map>? queryParameters]) { - // TODO: This could be moved to a seperate function - var uri = Uri.parse('${this.base}$url'); - // Because these are all final values, we can't just add the queryParameters and must instead build a new Uri Object every time this method is called. - var newUri = Uri( - scheme: uri.scheme, - userInfo: uri.userInfo, - host: uri.host, - port: uri.port, - path: uri.path, - query: uri.query, - queryParameters: queryParameters, - // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. - fragment: uri.fragment); - return http.get(newUri, headers: _headers) + final uri = Uri.parse('${this.base}$url').replace(queryParameters: queryParameters); + return http.get(uri, headers: _headers) .then(_handleResponse, onError: _handleError); } diff --git a/lib/api/labels.dart b/lib/api/labels.dart index a336601..617c91d 100644 --- a/lib/api/labels.dart +++ b/lib/api/labels.dart @@ -29,8 +29,8 @@ class LabelAPIService extends APIService implements LabelService { @override Future> getAll({String? query}) { - String? params = - query == null ? null : '?s=' + Uri.encodeQueryComponent(query); + String params = + query == null ? '' : '?s=' + Uri.encodeQueryComponent(query); return client.get('/labels$params').then( (response) => convertList(response.body, (result) => Label.fromJson(result))); } diff --git a/lib/components/AddDialog.dart b/lib/components/AddDialog.dart index 8182173..f4d0ef5 100644 --- a/lib/components/AddDialog.dart +++ b/lib/components/AddDialog.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:vikunja_app/components/datetimePicker.dart'; +import 'package:vikunja_app/global.dart'; import 'dart:developer'; import '../models/task.dart'; enum NewTaskDue {day,week, month, custom} +// TODO: add to enum above Map newTaskDueToDuration = { NewTaskDue.day: Duration(days: 1), NewTaskDue.week: Duration(days: 7), @@ -12,7 +14,7 @@ Map newTaskDueToDuration = { class AddDialog extends StatefulWidget { final ValueChanged? onAdd; - final ValueChanged? onAddTask; + final void Function(String title, DateTime? dueDate)? onAddTask; final InputDecoration? decoration; const AddDialog({Key? key, this.onAdd, this.decoration, this.onAddTask}) : super(key: key); @@ -65,11 +67,7 @@ class AddDialogState extends State { if (widget.onAdd != null && textController.text.isNotEmpty) widget.onAdd!(textController.text); if(widget.onAddTask != null && textController.text.isNotEmpty) { - widget.onAddTask!(Task(id: 0, - title: textController.text, - done: false, - createdBy: null, - dueDate: customDueDate, identifier: '')); + widget.onAddTask!(textController.text, customDueDate); } Navigator.pop(context); }, diff --git a/lib/components/BucketTaskCard.dart b/lib/components/BucketTaskCard.dart index a650ee1..7a2f55f 100644 --- a/lib/components/BucketTaskCard.dart +++ b/lib/components/BucketTaskCard.dart @@ -30,11 +30,7 @@ class BucketTaskCard extends StatefulWidget { required this.index, required this.onDragUpdate, required this.onAccept, - }) : assert(task != null), - assert(index != null), - assert(onDragUpdate != null), - assert(onAccept != null), - super(key: key); + }) : super(key: key); @override State createState() => _BucketTaskCardState(); @@ -61,9 +57,9 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive final identifierRow = Row( children: [ Text( - widget.task.identifier.isNotEmpty - ? '#${widget.task.identifier.substring(1)}' : '${widget.task.id}', - style: theme.textTheme.subtitle2?.copyWith( + (widget.task.identifier?.isNotEmpty ?? false) + ? '#${widget.task.identifier!.substring(1)}' : '${widget.task.id}', + style: (theme.textTheme.subtitle2 ?? TextStyle()).copyWith( color: Colors.grey, ), ), @@ -76,7 +72,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive child: FittedBox( child: Chip( label: Text('Done'), - labelStyle: theme.textTheme.labelLarge?.copyWith( + labelStyle: (theme.textTheme.labelLarge ?? TextStyle()).copyWith( fontWeight: FontWeight.bold, color: theme.brightness == Brightness.dark ? Colors.black : Colors.white, @@ -91,8 +87,8 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive children: [ Expanded( child: Text( - widget.task.title ?? "", - style: theme.textTheme.titleMedium?.copyWith( + widget.task.title, + style: (theme.textTheme.titleMedium ?? TextStyle(fontSize: 16)).copyWith( color: widget.task.textColor, ), ), @@ -112,7 +108,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive color: pastDue ? Colors.red : null, ), label: Text(durationToHumanReadable(duration)), - labelStyle: theme.textTheme.labelLarge?.copyWith( + labelStyle: (theme.textTheme.labelLarge ?? TextStyle()).copyWith( color: pastDue ? Colors.red : null, ), backgroundColor: pastDue ? Colors.red.withAlpha(20) : null, @@ -126,10 +122,10 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive spacing: 4, runSpacing: 4, ); - widget.task.labels?.sort((a, b) => a.title?.compareTo(b.title ?? "") ?? 0); - widget.task.labels?.asMap().forEach((i, label) { + widget.task.labels.sort((a, b) => a.title.compareTo(b.title)); + widget.task.labels.asMap().forEach((i, label) { labelRow.children.add(Chip( - label: Text(label.title ?? ""), + label: Text(label.title), labelStyle: theme.textTheme.labelLarge?.copyWith( color: label.textColor, ), @@ -137,7 +133,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive )); }); if (widget.task.hasCheckboxes) { - final checkboxStatistics = widget.task.checkboxStatistics!; + final checkboxStatistics = widget.task.checkboxStatistics; final iconSize = (theme.textTheme.labelLarge?.fontSize ?? 14) + 2; labelRow.children.add(Chip( avatar: Container( @@ -153,7 +149,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive ), )); } - if (widget.task.attachments != null && widget.task.attachments!.isNotEmpty) { + if (widget.task.attachments.isNotEmpty) { labelRow.children.add(Chip( label: Transform.rotate( angle: -pi / 4.0, @@ -161,7 +157,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive ), )); } - if (widget.task.description != null && widget.task.description!.isNotEmpty) { + if (widget.task.description.isNotEmpty) { labelRow.children.add(Chip( label: Icon(Icons.notes), )); @@ -237,7 +233,8 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive child: () { if (_dragging || _cardSize == null) return card; - final dropBoxSize = _dropData?.size ?? _cardSize; + final cardSize = _cardSize!; + final dropBoxSize = _dropData?.size ?? cardSize; final dropBox = DottedBorder( color: Colors.grey, child: SizedBox.fromSize(size: dropBoxSize), @@ -268,8 +265,8 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive }; return SizedBox( - width: _cardSize!.width, - height: _cardSize!.height + (dropAbove || dropBelow ? dropBoxSize!.height + 4 : 0), + width: cardSize.width, + height: cardSize.height + (dropAbove || dropBelow ? dropBoxSize.height + 4 : 0), child: Stack( children: [ Column( @@ -282,7 +279,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive Column( children: [ SizedBox( - height: (_cardSize!.height / 2) + (dropAbove ? dropBoxSize!.height : 0), + height: (cardSize.height / 2) + (dropAbove ? dropBoxSize.height : 0), child: DragTarget( onWillAccept: (data) => dragTargetOnWillAccept(data!, DropLocation.above), onAccept: dragTargetOnAccept, @@ -291,7 +288,7 @@ class _BucketTaskCardState extends State with AutomaticKeepAlive ), ), SizedBox( - height: (_cardSize!.height / 2) + (dropBelow ? dropBoxSize!.height : 0), + height: (cardSize.height / 2) + (dropBelow ? dropBoxSize.height : 0), child: DragTarget( onWillAccept: (data) => dragTargetOnWillAccept(data!, DropLocation.below), onAccept: dragTargetOnAccept, diff --git a/lib/components/SliverBucketList.dart b/lib/components/SliverBucketList.dart index 89c9033..6edd6ab 100644 --- a/lib/components/SliverBucketList.dart +++ b/lib/components/SliverBucketList.dart @@ -19,7 +19,6 @@ class SliverBucketList extends StatelessWidget { Widget build(BuildContext context) { return SliverList( delegate: SliverChildBuilderDelegate((context, index) { - if (bucket.tasks == null) return null; return index >= bucket.tasks.length ? null : BucketTaskCard( key: ObjectKey(bucket.tasks[index]), task: bucket.tasks[index], @@ -33,14 +32,16 @@ class SliverBucketList extends StatelessWidget { ); } - Future _moveTaskToBucket(BuildContext context, Task task, int index) { - return Provider.of(context, listen: false).moveTaskToBucket( + Future _moveTaskToBucket(BuildContext context, Task task, int index) async { + await Provider.of(context, listen: false).moveTaskToBucket( context: context, task: task, newBucketId: bucket.id, index: index, - ).then((_) => ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('${task.title} was moved to ${bucket.title} successfully!'), - ))); + )); } } diff --git a/lib/components/TaskTile.dart b/lib/components/TaskTile.dart index c7f6c3d..866d83a 100644 --- a/lib/components/TaskTile.dart +++ b/lib/components/TaskTile.dart @@ -14,9 +14,14 @@ class TaskTile extends StatefulWidget { final bool loading; final ValueSetter? onMarkedAsDone; - const TaskTile( - {Key? key, required this.task, required this.onEdit, this.loading = false, this.showInfo = false, this.onMarkedAsDone}) - : super(key: key); + const TaskTile({ + Key? key, + required this.task, + required this.onEdit, + this.loading = false, + this.showInfo = false, + this.onMarkedAsDone, + }) : super(key: key); /* @override TaskTileState createState() { @@ -49,11 +54,11 @@ class TaskTileState extends State with AutomaticKeepAliveClientMixin { strokeWidth: 2.0, )), ), - title: Text(_currentTask.title ?? ""), + title: Text(_currentTask.title), subtitle: - _currentTask.description == null || _currentTask.description!.isEmpty + _currentTask.description.isEmpty ? null - : Text(_currentTask.description ?? ""), + : Text(_currentTask.description), trailing: IconButton( icon: Icon(Icons.settings), onPressed: () { }, ), @@ -73,14 +78,14 @@ class TaskTileState extends State with AutomaticKeepAliveClientMixin { color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black, ), ) - ) : Text(_currentTask.title ?? ""), + ) : Text(_currentTask.title), controlAffinity: ListTileControlAffinity.leading, value: _currentTask.done, subtitle: widget.showInfo && _currentTask.hasDueDate ? - Text("Due " + durationToHumanReadable(durationUntilDue!), style: TextStyle(color: durationUntilDue.isNegative ? Colors.red : null),) - : _currentTask.description == null || _currentTask.description!.isEmpty + Text("Due " + durationToHumanReadable(durationUntilDue!), style: durationUntilDue.isNegative ? TextStyle(color: Colors.red) : null,) + : _currentTask.description.isEmpty ? null - : Text(_currentTask.description ?? ""), + : Text(_currentTask.description), secondary: IconButton(icon: Icon(Icons.settings), onPressed: () { Navigator.push( diff --git a/lib/components/label.dart b/lib/components/label.dart index 39a92dd..7cac52b 100644 --- a/lib/components/label.dart +++ b/lib/components/label.dart @@ -1,40 +1,28 @@ import 'package:flutter/material.dart'; import 'package:vikunja_app/models/label.dart'; -import 'package:vikunja_app/theme/constants.dart'; -class LabelComponent extends StatefulWidget { +class LabelComponent extends StatelessWidget { final Label label; final VoidCallback onDelete; const LabelComponent({Key? key, required this.label, required this.onDelete}) : super(key: key); - @override - State createState() { - return new LabelComponentState(); - } -} - -class LabelComponentState extends State { @override Widget build(BuildContext context) { - Color backgroundColor = widget.label.color; - Color textColor = - backgroundColor.computeLuminance() > 0.5 ? vLabelDark : vLabelLight; - return Chip( label: Text( - widget.label.title ?? "", + label.title, style: TextStyle( - color: textColor, + color: label.textColor, ), ), - backgroundColor: backgroundColor, + backgroundColor: label.color, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(3)), ), - onDeleted: widget.onDelete, - deleteIconColor: textColor, + onDeleted: onDelete, + deleteIconColor: label.textColor, deleteIcon: Container( padding: EdgeInsets.all(3), decoration: BoxDecoration( @@ -43,7 +31,7 @@ class LabelComponentState extends State { ), child: Icon( Icons.close, - color: textColor, + color: label.textColor, size: 15, ), ), diff --git a/lib/global.dart b/lib/global.dart index 2e7beb3..c1612c9 100644 --- a/lib/global.dart +++ b/lib/global.dart @@ -158,29 +158,33 @@ class VikunjaGlobalState extends State { requestIOSPermissions(notificationsPlugin); } - void scheduleDueNotifications() { - notificationsPlugin.cancelAll().then((value) { - taskService.getAll().then((value) => - value.forEach((task) { - if(task.reminderDates != null) - task.reminderDates!.forEach((reminder) { - scheduleNotification("Reminder", "This is your reminder for '" + task.title! + "'", - notificationsPlugin, - reminder!, - currentTimeZone, - platformChannelSpecificsReminders, - id: (reminder.millisecondsSinceEpoch/1000).floor()); - }); - if(task.dueDate != null) - scheduleNotification("Due Reminder","The task '" + task.title! + "' is due.", - notificationsPlugin, - task.dueDate!, - currentTimeZone, - platformChannelSpecificsDueDate, - id: task.id); - }) - ); - }); + Future scheduleDueNotifications() async { + await notificationsPlugin.cancelAll(); + final tasks = await taskService.getAll(); + for (final task in tasks) { + for (final reminder in task.reminderDates) { + scheduleNotification( + "Reminder", + "This is your reminder for '" + task.title + "'", + notificationsPlugin, + reminder, + currentTimeZone, + platformChannelSpecificsReminders, + id: (reminder.millisecondsSinceEpoch / 1000).floor(), + ); + } + if (task.hasDueDate) { + scheduleNotification( + "Due Reminder", + "The task '" + task.title + "' is due.", + notificationsPlugin, + task.dueDate!, + currentTimeZone, + platformChannelSpecificsDueDate, + id: task.id, + ); + } + } } @@ -215,7 +219,7 @@ class VikunjaGlobalState extends State { return; } client.configure(token: token, base: base, authenticated: true); - var loadedCurrentUser; + User loadedCurrentUser; try { loadedCurrentUser = await UserAPIService(client).getCurrentUser(); } on ApiException catch (e) { @@ -233,9 +237,9 @@ class VikunjaGlobalState extends State { }); return; } - loadedCurrentUser = User(int.tryParse(currentUser)!, "", ""); + loadedCurrentUser = User(id: int.parse(currentUser), username: ''); } catch (otherExceptions) { - loadedCurrentUser = User(int.tryParse(currentUser)!, "", ""); + loadedCurrentUser = User(id: int.parse(currentUser), username: ''); } setState(() { _currentUser = loadedCurrentUser; diff --git a/lib/models/bucket.dart b/lib/models/bucket.dart index 6fcbbab..2568640 100644 --- a/lib/models/bucket.dart +++ b/lib/models/bucket.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:vikunja_app/models/task.dart'; @@ -7,55 +6,58 @@ import 'package:vikunja_app/models/user.dart'; @JsonSerializable() class Bucket { int id, listId, limit; - String? title; - double position; - DateTime? created, updated; - User? createdBy; + String title; + double? position; + late final DateTime created, updated; + User createdBy; bool isDoneBucket; + late final List tasks; + Bucket({ - required this.id, + this.id = -1, required this.listId, - this.title, - this.position = 0, + required this.title, + this.position, required this.limit, this.isDoneBucket = false, - this.created, - this.updated, - this.createdBy, - this.tasks = const [], - }); - - List tasks = []; + DateTime? created, + DateTime? updated, + required this.createdBy, + List? tasks, + }) { + this.created = created ?? DateTime.now(); + this.updated = created ?? DateTime.now(); + this.tasks = tasks ?? []; + } Bucket.fromJSON(Map json) - : id = json['id'], - listId = json['list_id'], - title = json['title'], - position = json['position'] is int - ? json['position'].toDouble() - : json['position'], - limit = json['limit'], - isDoneBucket = json['is_done_bucket'], - created = DateTime.parse(json['created']), - updated = DateTime.parse(json['updated']), - createdBy = json['created_by'] == null - ? null - : User.fromJson(json['created_by']), - tasks = (((json['tasks'] == null) ? [] : json['tasks']) as List) - .map((task) => Task.fromJson(task)) - .cast() - .toList(); + : id = json['id'], + listId = json['list_id'], + title = json['title'], + position = json['position'] is int + ? json['position'].toDouble() + : json['position'], + limit = json['limit'], + isDoneBucket = json['is_done_bucket'], + created = DateTime.parse(json['created']), + updated = DateTime.parse(json['updated']), + createdBy = User.fromJson(json['created_by']), + tasks = json['tasks'] == null + ? [] + : (json['tasks'] as List) + .map((task) => Task.fromJson(task)) + .toList(); toJSON() => { - 'id': id, + 'id': id != -1 ? id : null, 'list_id': listId, 'title': title, 'position': position, 'limit': limit, 'is_done_bucket': isDoneBucket, - 'created': created?.toUtc().toIso8601String(), - 'updated': updated?.toUtc().toIso8601String(), - 'createdBy': createdBy?.toJSON(), + 'created': created.toUtc().toIso8601String(), + 'updated': updated.toUtc().toIso8601String(), + 'created_by': createdBy.toJSON(), 'tasks': tasks.map((task) => task.toJSON()).toList(), }; } \ No newline at end of file diff --git a/lib/models/label.dart b/lib/models/label.dart index ebce47e..2d3ad0f 100644 --- a/lib/models/label.dart +++ b/lib/models/label.dart @@ -5,42 +5,45 @@ import 'package:vikunja_app/theme/constants.dart'; class Label { final int id; - final String? title, description; - final DateTime? created, updated; - final User? createdBy; - final Color color; + final String title, description; + late final DateTime created, updated; + final User createdBy; + final Color? color; - Label( - { - required this.id, - this.title, - this.description, - this.color = vLabelDefaultColor, - this.created, - this.updated, - this.createdBy}); + late final Color textColor = color != null && color!.computeLuminance() <= 0.5 ? vLabelLight : vLabelDark; + + Label({ + this.id = -1, + required this.title, + this.description = '', + this.color, + DateTime? created, + DateTime? updated, + required this.createdBy, + }) { + this.created = created ?? DateTime.now(); + this.updated = updated ?? DateTime.now(); + } Label.fromJson(Map json) : id = json['id'], title = json['title'], description = json['description'], color = json['hex_color'] == '' - ? vLabelDefaultColor + ? null : new Color(int.parse(json['hex_color'], radix: 16) + 0xFF000000), updated = DateTime.parse(json['updated']), created = DateTime.parse(json['created']), createdBy = User.fromJson(json['created_by']); toJSON() => { - 'id': id, + 'id': id != -1 ? id : null, 'title': title, 'description': description, 'hex_color': - color.value.toRadixString(16).padLeft(8, '0').substring(2), - 'created_by': createdBy?.toJSON(), - 'updated': updated?.toUtc().toIso8601String(), - 'created': created?.toUtc().toIso8601String(), + color?.value.toRadixString(16).padLeft(8, '0').substring(2), + 'created_by': createdBy.toJSON(), + 'updated': updated.toUtc().toIso8601String(), + 'created': created.toUtc().toIso8601String(), }; - - Color get textColor => color != null && color.computeLuminance() <= 0.5 ? vLabelLight : vLabelDark; } diff --git a/lib/models/labelTask.dart b/lib/models/labelTask.dart index 2209052..092f456 100644 --- a/lib/models/labelTask.dart +++ b/lib/models/labelTask.dart @@ -1,6 +1,7 @@ import 'package:meta/meta.dart'; import 'package:vikunja_app/models/label.dart'; import 'package:vikunja_app/models/task.dart'; +import 'package:vikunja_app/models/user.dart'; class LabelTask { final Label label; @@ -8,8 +9,8 @@ class LabelTask { LabelTask({required this.label, required this.task}); - LabelTask.fromJson(Map json) - : label = new Label(id: json['label_id']), + LabelTask.fromJson(Map json, User createdBy) + : label = new Label(id: json['label_id'], title: '', createdBy: createdBy), task = null; toJSON() => { diff --git a/lib/models/list.dart b/lib/models/list.dart index 0dfeb03..dad3855 100644 --- a/lib/models/list.dart +++ b/lib/models/list.dart @@ -1,50 +1,53 @@ -import 'package:meta/meta.dart'; 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; - List tasks; + String title, description; + final User owner; + late final DateTime created, updated; + late final List tasks; final bool isFavorite; TaskList({ - required this.id, + this.id = -1, required this.title, required this.namespaceId, - this.description, - this.owner, - this.created, - this.updated, - this.tasks = const [], + this.description = '', + required this.owner, + DateTime? created, + DateTime? updated, + List? tasks, this.isFavorite = false, - }); + }) { + this.created = created ?? DateTime.now(); + this.updated = updated ?? DateTime.now(); + this.tasks = tasks ?? []; + } TaskList.fromJson(Map json) : id = json['id'], - owner = json['owner'] == null ? null : User.fromJson(json['owner']), + 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) + tasks = json['tasks'] == null ? [] : (json['tasks'] as List) .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(), - "namespace_id": this.namespaceId + 'id': id != -1 ? id : null, + 'title': title, + 'description': description, + 'owner': owner.toJSON(), + 'created': created.toIso8601String(), + 'updated': updated.toIso8601String(), + 'namespace_id': namespaceId }; } } diff --git a/lib/models/namespace.dart b/lib/models/namespace.dart index 0cac4be..223aa0a 100644 --- a/lib/models/namespace.dart +++ b/lib/models/namespace.dart @@ -1,19 +1,22 @@ import 'package:vikunja_app/models/user.dart'; -import 'package:meta/meta.dart'; class Namespace { final int id; - final DateTime? created, updated; - final String? title, description; - final User? owner; + late final DateTime created, updated; + final String title, description; + final User owner; - Namespace( - {required this.id, - this.created, - this.updated, - required this.title, - this.description, - this.owner}); + Namespace({ + this.id = -1, + 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 json) : title = json['title'], @@ -21,13 +24,14 @@ class Namespace { id = json['id'], created = DateTime.parse(json['created']), updated = DateTime.parse(json['updated']), - owner = json['owner'] == null ? null : User.fromJson(json['owner']); + owner = User.fromJson(json['owner']); toJSON() => { - "created": created?.toIso8601String(), - "updated": updated?.toIso8601String(), - "title": title, - "owner": owner?.toJSON(), - "description": description + 'id': id != -1 ? id : null, + 'created': created.toIso8601String(), + 'updated': updated.toIso8601String(), + 'title': title, + 'owner': owner.toJSON(), + 'description': description }; } diff --git a/lib/models/task.dart b/lib/models/task.dart index 7ffdeb2..0802c9b 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -1,57 +1,63 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:vikunja_app/components/date_extension.dart'; import 'package:vikunja_app/models/label.dart'; import 'package:vikunja_app/models/user.dart'; import 'package:vikunja_app/models/taskAttachment.dart'; -import 'package:vikunja_app/theme/constants.dart'; import 'package:vikunja_app/utils/checkboxes_in_text.dart'; @JsonSerializable() class Task { - final int? id, parentTaskId, priority, listId, bucketId; - final DateTime? created, updated, dueDate, startDate, endDate; - final List? reminderDates; - final String identifier; - final String? title, description; + final int id; + final int? parentTaskId, priority, bucketId; + final int listId; + late final DateTime created, updated; + final DateTime? dueDate, startDate, endDate; + final List reminderDates; + final String? identifier; + final String title, description; final bool done; - final Color color; + final Color? color; final double? kanbanPosition; - final User? createdBy; + final User createdBy; final Duration? repeatAfter; - final List? subtasks; - final List