mirror of
https://github.com/go-vikunja/app
synced 2024-06-06 04:29:47 +00:00
Merge pull request #16 from k9withabone/null-safety-migration
null-safety & some other cleanup
This commit is contained in:
commit
f9c9cdc942
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -4,7 +4,11 @@
|
|||
"name": "Flutter",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug"
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"main"
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,13 +16,13 @@ class Client {
|
|||
GlobalKey<ScaffoldMessengerState> 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<Response> get(String url,
|
||||
[Map<String, List<String>>? 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<String, String> 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ class LabelAPIService extends APIService implements LabelService {
|
|||
|
||||
@override
|
||||
Future<List<Label>> 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)));
|
||||
}
|
||||
|
|
|
@ -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<NewTaskDue, Duration> newTaskDueToDuration = {
|
||||
NewTaskDue.day: Duration(days: 1),
|
||||
NewTaskDue.week: Duration(days: 7),
|
||||
|
@ -12,7 +14,7 @@ Map<NewTaskDue, Duration> newTaskDueToDuration = {
|
|||
|
||||
class AddDialog extends StatefulWidget {
|
||||
final ValueChanged<String>? onAdd;
|
||||
final ValueChanged<Task>? 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<AddDialog> {
|
|||
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);
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ class _BucketLimitDialogState extends State<BucketLimitDialog> {
|
|||
Widget build(BuildContext context) {
|
||||
if (_controller.text.isEmpty) _controller.text = '${widget.bucket.limit}';
|
||||
return AlertDialog(
|
||||
title: Text('Limit for ${widget.bucket.title}'),
|
||||
title: Text('Limit for \'${widget.bucket.title}\''),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
|
@ -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<BucketTaskCard> createState() => _BucketTaskCardState();
|
||||
|
@ -63,7 +59,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
Text(
|
||||
widget.task.identifier.isNotEmpty
|
||||
? '#${widget.task.identifier.substring(1)}' : '${widget.task.id}',
|
||||
style: theme.textTheme.subtitle2?.copyWith(
|
||||
style: (theme.textTheme.subtitle2 ?? TextStyle()).copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
|
@ -76,7 +72,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> 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<BucketTaskCard> with AutomaticKeepAlive
|
|||
children: <Widget>[
|
||||
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<BucketTaskCard> 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<BucketTaskCard> 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<BucketTaskCard> 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<BucketTaskCard> 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<BucketTaskCard> 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<BucketTaskCard> 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<BucketTaskCard> 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: <Widget>[
|
||||
Column(
|
||||
|
@ -282,7 +279,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
Column(
|
||||
children: <SizedBox>[
|
||||
SizedBox(
|
||||
height: (_cardSize!.height / 2) + (dropAbove ? dropBoxSize!.height : 0),
|
||||
height: (cardSize.height / 2) + (dropAbove ? dropBoxSize.height : 0),
|
||||
child: DragTarget<TaskData>(
|
||||
onWillAccept: (data) => dragTargetOnWillAccept(data!, DropLocation.above),
|
||||
onAccept: dragTargetOnAccept,
|
||||
|
@ -291,7 +288,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: (_cardSize!.height / 2) + (dropBelow ? dropBoxSize!.height : 0),
|
||||
height: (cardSize.height / 2) + (dropBelow ? dropBoxSize.height : 0),
|
||||
child: DragTarget<TaskData>(
|
||||
onWillAccept: (data) => dragTargetOnWillAccept(data!, DropLocation.below),
|
||||
onAccept: dragTargetOnAccept,
|
||||
|
|
|
@ -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<void> _moveTaskToBucket(BuildContext context, Task task, int index) {
|
||||
return Provider.of<ListProvider>(context, listen: false).moveTaskToBucket(
|
||||
Future<void> _moveTaskToBucket(BuildContext context, Task task, int index) async {
|
||||
await Provider.of<ListProvider>(context, listen: false).moveTaskToBucket(
|
||||
context: context,
|
||||
task: task,
|
||||
newBucketId: bucket.id,
|
||||
index: index,
|
||||
).then((_) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('${task.title} was moved to ${bucket.title} successfully!'),
|
||||
)));
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('\'${task.title}\' was moved to \'${bucket.title}\' successfully!'),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,14 @@ class TaskTile extends StatefulWidget {
|
|||
final bool loading;
|
||||
final ValueSetter<bool>? 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<TaskTile> 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<TaskTile> 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<Task>(
|
||||
|
|
|
@ -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<StatefulWidget> createState() {
|
||||
return new LabelComponentState();
|
||||
}
|
||||
}
|
||||
|
||||
class LabelComponentState extends State<LabelComponent> {
|
||||
@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<LabelComponent> {
|
|||
),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: textColor,
|
||||
color: label.textColor,
|
||||
size: 15,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -158,29 +158,33 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
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<void> 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<VikunjaGlobal> {
|
|||
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<VikunjaGlobal> {
|
|||
});
|
||||
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;
|
||||
|
|
|
@ -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,56 @@ 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;
|
||||
final DateTime created, updated;
|
||||
User createdBy;
|
||||
bool isDoneBucket;
|
||||
final List<Task> 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 <Task>[],
|
||||
});
|
||||
|
||||
List<Task> tasks = [];
|
||||
DateTime? created,
|
||||
DateTime? updated,
|
||||
required this.createdBy,
|
||||
List<Task>? tasks,
|
||||
}) : this.created = created ?? DateTime.now(),
|
||||
this.updated = created ?? DateTime.now(),
|
||||
this.tasks = tasks ?? [];
|
||||
|
||||
Bucket.fromJSON(Map<String, dynamic> 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<dynamic>)
|
||||
.map((task) => Task.fromJson(task))
|
||||
.cast<Task>()
|
||||
.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<dynamic>)
|
||||
.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(),
|
||||
};
|
||||
}
|
|
@ -5,42 +5,43 @@ 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;
|
||||
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<String, dynamic> 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;
|
||||
}
|
||||
|
|
|
@ -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<String, dynamic> json)
|
||||
: label = new Label(id: json['label_id']),
|
||||
LabelTask.fromJson(Map<String, dynamic> json, User createdBy)
|
||||
: label = new Label(id: json['label_id'], title: '', createdBy: createdBy),
|
||||
task = null;
|
||||
|
||||
toJSON() => {
|
||||
|
|
|
@ -1,50 +1,51 @@
|
|||
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<Task?> tasks;
|
||||
String title, description;
|
||||
final User owner;
|
||||
final DateTime created, updated;
|
||||
final List<Task> 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 <Task>[],
|
||||
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 = 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<dynamic>)
|
||||
tasks = json['tasks'] == null ? [] : (json['tasks'] as List<dynamic>)
|
||||
.map((taskJson) => Task.fromJson(taskJson))
|
||||
.toList();
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
"id": this.id,
|
||||
"title": this.title,
|
||||
"description": this.description,
|
||||
"owner": this.owner?.toJSON(),
|
||||
"created": this.created?.toIso8601String(),
|
||||
"updated": this.updated?.toIso8601String(),
|
||||
"namespace_id": this.namespaceId
|
||||
'id': id != -1 ? id : null,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'owner': owner.toJSON(),
|
||||
'created': created.toUtc().toIso8601String(),
|
||||
'updated': updated.toUtc().toIso8601String(),
|
||||
'namespace_id': namespaceId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
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;
|
||||
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<String, dynamic> json)
|
||||
: title = json['title'],
|
||||
|
@ -21,13 +22,32 @@ 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
|
||||
Map<String, dynamic> toJSON() => {
|
||||
'id': id != -1 ? id : null,
|
||||
'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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,61 @@
|
|||
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<DateTime?>? reminderDates;
|
||||
final int id;
|
||||
final int? parentTaskId, priority, bucketId;
|
||||
final int listId;
|
||||
final DateTime created, updated;
|
||||
final DateTime? dueDate, startDate, endDate;
|
||||
final List<DateTime> reminderDates;
|
||||
final String identifier;
|
||||
final String? title, description;
|
||||
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<Task>? subtasks;
|
||||
final List<Label>? labels;
|
||||
final List<TaskAttachment>? attachments;
|
||||
final List<Task> subtasks;
|
||||
final List<Label> labels;
|
||||
final List<TaskAttachment> attachments;
|
||||
// TODO: add position(?)
|
||||
|
||||
CheckboxStatistics? _checkboxStatistics;
|
||||
late final checkboxStatistics = getCheckboxStatistics(description);
|
||||
late final hasCheckboxes = checkboxStatistics.total != 0;
|
||||
late final textColor = (color != null && color!.computeLuminance() > 0.5) ? Colors.black : Colors.white;
|
||||
late final hasDueDate = dueDate?.year != 1;
|
||||
|
||||
// // TODO: use `late final` once upgraded to current dart version
|
||||
Task({
|
||||
required this.id,
|
||||
required this.identifier,
|
||||
this.title,
|
||||
this.description,
|
||||
this.id = -1,
|
||||
this.identifier = '',
|
||||
this.title = '',
|
||||
this.description = '',
|
||||
this.done = false,
|
||||
this.reminderDates,
|
||||
this.reminderDates = const [],
|
||||
this.dueDate,
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.parentTaskId,
|
||||
this.priority,
|
||||
this.repeatAfter,
|
||||
this.color = vBlue, // TODO: decide on color
|
||||
this.color,
|
||||
this.kanbanPosition,
|
||||
this.subtasks,
|
||||
this.labels,
|
||||
this.attachments,
|
||||
this.created,
|
||||
this.updated,
|
||||
this.createdBy,
|
||||
this.listId,
|
||||
this.subtasks = const [],
|
||||
this.labels = const [],
|
||||
this.attachments = const [],
|
||||
DateTime? created,
|
||||
DateTime? updated,
|
||||
required this.createdBy,
|
||||
required this.listId,
|
||||
this.bucketId,
|
||||
});
|
||||
}) : this.created = created ?? DateTime.now(),
|
||||
this.updated = updated ?? DateTime.now();
|
||||
|
||||
bool loading = false;
|
||||
|
||||
|
@ -61,93 +65,85 @@ class Task {
|
|||
description = json['description'],
|
||||
identifier = json['identifier'],
|
||||
done = json['done'],
|
||||
reminderDates = json['reminder_dates'] != null ? (json['reminder_dates'] as List<dynamic>)
|
||||
.map((ts) => DateTime.parse(ts))
|
||||
.cast<DateTime>()
|
||||
.toList() : null,
|
||||
reminderDates = json['reminder_dates'] != null
|
||||
? (json['reminder_dates'] as List<dynamic>)
|
||||
.map((ts) => DateTime.parse(ts))
|
||||
.toList()
|
||||
: [],
|
||||
dueDate = DateTime.parse(json['due_date']),
|
||||
startDate = DateTime.parse(json['start_date']),
|
||||
endDate = DateTime.parse(json['end_date']),
|
||||
parentTaskId = json['parent_task_id'],
|
||||
priority = json['priority'],
|
||||
repeatAfter = Duration(seconds: json['repeat_after']),
|
||||
color = json['hex_color'] == ''
|
||||
? vBlue
|
||||
: new Color(int.parse(json['hex_color'], radix: 16) + 0xFF000000),
|
||||
color = json['hex_color'] != ''
|
||||
? Color(int.parse(json['hex_color'], radix: 16) + 0xFF000000)
|
||||
: null,
|
||||
kanbanPosition = json['kanban_position'] is int
|
||||
? json['kanban_position'].toDouble()
|
||||
: json['kanban_position'],
|
||||
labels = ((json['labels'] ?? []) as List<dynamic>)
|
||||
.map((label) => Label.fromJson(label))
|
||||
.cast<Label>()
|
||||
.toList(),
|
||||
subtasks = ((json['subtasks'] ?? []) as List<dynamic>)
|
||||
.map((subtask) => Task.fromJson(subtask))
|
||||
.cast<Task>()
|
||||
.toList(),
|
||||
attachments = ((json['attachments'] ?? []) as List<dynamic>)
|
||||
.map((attachment) => TaskAttachment.fromJSON(attachment))
|
||||
.cast<TaskAttachment>()
|
||||
.toList(),
|
||||
labels = json['labels'] != null
|
||||
? (json['labels'] as List<dynamic>)
|
||||
.map((label) => Label.fromJson(label))
|
||||
.toList()
|
||||
: [],
|
||||
subtasks = json['subtasks'] != null
|
||||
? (json['subtasks'] as List<dynamic>)
|
||||
.map((subtask) => Task.fromJson(subtask))
|
||||
.toList()
|
||||
: [],
|
||||
attachments = json['attachments'] != null
|
||||
? (json['attachments'] as List<dynamic>)
|
||||
.map((attachment) => TaskAttachment.fromJSON(attachment))
|
||||
.toList()
|
||||
: [],
|
||||
updated = DateTime.parse(json['updated']),
|
||||
created = DateTime.parse(json['created']),
|
||||
listId = json['list_id'],
|
||||
bucketId = json['bucket_id'],
|
||||
createdBy = json['created_by'] == null
|
||||
? null
|
||||
: User.fromJson(json['created_by']);
|
||||
createdBy = User.fromJson(json['created_by']);
|
||||
|
||||
toJSON() => {
|
||||
'id': id,
|
||||
'id': id != -1 ? id : null,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'identifier': identifier,
|
||||
'identifier': identifier.isNotEmpty ? identifier : null,
|
||||
'done': done,
|
||||
'reminder_dates':
|
||||
reminderDates?.map((date) => date?.toUtc().toIso8601String()).toList(),
|
||||
'reminder_dates': reminderDates
|
||||
.map((date) => date.toUtc().toIso8601String())
|
||||
.toList(),
|
||||
'due_date': dueDate?.toUtc().toIso8601String(),
|
||||
'start_date': startDate?.toUtc().toIso8601String(),
|
||||
'end_date': endDate?.toUtc().toIso8601String(),
|
||||
'priority': priority,
|
||||
'repeat_after': repeatAfter?.inSeconds,
|
||||
'hex_color': color.value.toRadixString(16).padLeft(8, '0').substring(2),
|
||||
'hex_color': color?.value.toRadixString(16).padLeft(8, '0').substring(2),
|
||||
'kanban_position': kanbanPosition,
|
||||
'labels': labels?.map((label) => label.toJSON()).toList(),
|
||||
'subtasks': subtasks?.map((subtask) => subtask.toJSON()).toList(),
|
||||
'attachments': attachments?.map((attachment) => attachment.toJSON()).toList(),
|
||||
'labels': labels.map((label) => label.toJSON()).toList(),
|
||||
'subtasks': subtasks.map((subtask) => subtask.toJSON()).toList(),
|
||||
'attachments':
|
||||
attachments.map((attachment) => attachment.toJSON()).toList(),
|
||||
'bucket_id': bucketId,
|
||||
'created_by': createdBy?.toJSON(),
|
||||
'updated': updated?.toUtc().toIso8601String(),
|
||||
'created': created?.toUtc().toIso8601String(),
|
||||
'created_by': createdBy.toJSON(),
|
||||
'updated': updated.toUtc().toIso8601String(),
|
||||
'created': created.toUtc().toIso8601String(),
|
||||
};
|
||||
|
||||
Color? get textColor => color.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||
|
||||
CheckboxStatistics? get checkboxStatistics {
|
||||
if (_checkboxStatistics != null)
|
||||
return _checkboxStatistics;
|
||||
if (description!.isEmpty)
|
||||
return null;
|
||||
|
||||
_checkboxStatistics = getCheckboxStatistics(description!);
|
||||
return _checkboxStatistics;
|
||||
}
|
||||
|
||||
bool get hasCheckboxes {
|
||||
final checkboxStatistics = this.checkboxStatistics;
|
||||
if (checkboxStatistics != null && checkboxStatistics.total != 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get hasDueDate => dueDate?.year != 1;
|
||||
|
||||
Task copyWith({
|
||||
int? id, int? parentTaskId, int? priority, int? listId, int? bucketId,
|
||||
DateTime? created, DateTime? updated, DateTime? dueDate, DateTime? startDate, DateTime? endDate,
|
||||
List<DateTime?>? reminderDates,
|
||||
String? title, String? description, String? identifier,
|
||||
int? id,
|
||||
int? parentTaskId,
|
||||
int? priority,
|
||||
int? listId,
|
||||
int? bucketId,
|
||||
DateTime? created,
|
||||
DateTime? updated,
|
||||
DateTime? dueDate,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
List<DateTime>? reminderDates,
|
||||
String? title,
|
||||
String? description,
|
||||
String? identifier,
|
||||
bool? done,
|
||||
Color? color,
|
||||
bool? resetColor,
|
||||
|
@ -160,27 +156,27 @@ class Task {
|
|||
}) {
|
||||
return Task(
|
||||
id: id ?? this.id,
|
||||
parentTaskId: parentTaskId,
|
||||
priority: priority,
|
||||
listId: listId,
|
||||
bucketId: bucketId,
|
||||
created: created,
|
||||
updated: updated,
|
||||
dueDate: dueDate,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
reminderDates: reminderDates,
|
||||
title: title,
|
||||
description: description,
|
||||
parentTaskId: parentTaskId ?? this.parentTaskId,
|
||||
priority: priority ?? this.priority,
|
||||
listId: listId ?? this.listId,
|
||||
bucketId: bucketId ?? this.bucketId,
|
||||
created: created ?? this.created,
|
||||
updated: updated ?? this.updated,
|
||||
dueDate: dueDate ?? this.dueDate,
|
||||
startDate: startDate ?? this.startDate,
|
||||
endDate: endDate ?? this.endDate,
|
||||
reminderDates: reminderDates ?? this.reminderDates,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
identifier: identifier ?? this.identifier,
|
||||
done: done ?? this.done,
|
||||
color: (resetColor ?? false) ? vBlue : (color ?? this.color),
|
||||
kanbanPosition: kanbanPosition,
|
||||
createdBy: createdBy,
|
||||
repeatAfter: repeatAfter,
|
||||
subtasks: subtasks,
|
||||
labels: labels,
|
||||
attachments: attachments,
|
||||
color: (resetColor ?? false) ? null : (color ?? this.color),
|
||||
kanbanPosition: kanbanPosition ?? this.kanbanPosition,
|
||||
createdBy: createdBy ?? this.createdBy,
|
||||
repeatAfter: repeatAfter ?? this.repeatAfter,
|
||||
subtasks: subtasks ?? this.subtasks,
|
||||
labels: labels ?? this.labels,
|
||||
attachments: attachments ?? this.attachments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:vikunja_app/models/user.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class TaskAttachment {
|
||||
int id, taskId;
|
||||
DateTime? created;
|
||||
User? createdBy;
|
||||
final int id, taskId;
|
||||
final DateTime created;
|
||||
final User createdBy;
|
||||
// TODO: add file
|
||||
|
||||
TaskAttachment({
|
||||
required this.id,
|
||||
this.id = -1,
|
||||
required this.taskId,
|
||||
this.created,
|
||||
this.createdBy,
|
||||
});
|
||||
DateTime? created,
|
||||
required this.createdBy,
|
||||
}) : this.created = created ?? DateTime.now();
|
||||
|
||||
TaskAttachment.fromJSON(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
taskId = json['task_id'],
|
||||
created = DateTime.parse(json['created']),
|
||||
createdBy = json['created_by'] == null
|
||||
? null
|
||||
: User.fromJson(json['created_by']);
|
||||
createdBy = User.fromJson(json['created_by']);
|
||||
|
||||
toJSON() => {
|
||||
'id': id,
|
||||
'id': id != -1 ? id : null,
|
||||
'task_id': taskId,
|
||||
'created': created?.toUtc().toIso8601String(),
|
||||
'created_by': createdBy?.toJSON(),
|
||||
'created': created.toUtc().toIso8601String(),
|
||||
'created_by': createdBy.toJSON(),
|
||||
};
|
||||
}
|
|
@ -3,18 +3,35 @@ import 'package:vikunja_app/global.dart';
|
|||
|
||||
class User {
|
||||
final int id;
|
||||
final String email, username;
|
||||
final String name, username;
|
||||
final DateTime created, updated;
|
||||
|
||||
User({
|
||||
this.id = -1,
|
||||
this.name = '',
|
||||
required this.username,
|
||||
DateTime? created,
|
||||
DateTime? updated,
|
||||
}) : this.created = created ?? DateTime.now(),
|
||||
this.updated = updated ?? DateTime.now();
|
||||
|
||||
User(this.id, this.email, this.username);
|
||||
User.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
email = json.containsKey('email') ? json['email'] : '',
|
||||
username = json['username'];
|
||||
name = json.containsKey('name') ? json['name'] : '',
|
||||
username = json['username'],
|
||||
created = DateTime.parse(json['created']),
|
||||
updated = DateTime.parse(json['updated']);
|
||||
|
||||
toJSON() => {"id": this.id, "email": this.email, "username": this.username};
|
||||
toJSON() => {
|
||||
'id': id != -1 ? id : null,
|
||||
'name': name,
|
||||
'username': username,
|
||||
'created': created.toUtc().toIso8601String(),
|
||||
'updated': updated.toUtc().toIso8601String(),
|
||||
};
|
||||
|
||||
String? avatarUrl(BuildContext context) {
|
||||
return VikunjaGlobal.of(context).client.base! + "/avatar/${this.username}";
|
||||
String avatarUrl(BuildContext context) {
|
||||
return VikunjaGlobal.of(context).client.base + "/avatar/${this.username}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
.asMap()
|
||||
.forEach((i, namespace) => namespacesList.add(new ListTile(
|
||||
leading: const Icon(Icons.folder),
|
||||
title: new Text(namespace.title ?? ""),
|
||||
title: new Text(namespace.title),
|
||||
selected: i == _selectedDrawerIndex,
|
||||
onTap: () => _onSelectItem(i),
|
||||
)));
|
||||
|
@ -87,7 +87,7 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var currentUser = VikunjaGlobal.of(context).currentUser;
|
||||
final currentUser = VikunjaGlobal.of(context).currentUser;
|
||||
if (_selectedDrawerIndex != _previousDrawerIndex || drawerItem == null)
|
||||
drawerItem = _getDrawerItemWidget(_selectedDrawerIndex);
|
||||
|
||||
|
@ -107,15 +107,15 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
))).whenComplete(() => _loadNamespaces()))
|
||||
],
|
||||
),
|
||||
drawer: new Drawer(
|
||||
child: new Column(children: <Widget>[
|
||||
new UserAccountsDrawerHeader(
|
||||
accountEmail: currentUser?.email == null
|
||||
? null
|
||||
: Text(currentUser?.email ?? ""),
|
||||
accountName: currentUser?.username == null
|
||||
? null
|
||||
: Text(currentUser?.username ?? ""),
|
||||
drawer: Drawer(
|
||||
child: Column(children: <Widget>[
|
||||
UserAccountsDrawerHeader(
|
||||
accountName: currentUser != null
|
||||
? Text(currentUser.username)
|
||||
: null,
|
||||
accountEmail: currentUser != null
|
||||
? Text(currentUser.name)
|
||||
: null,
|
||||
onDetailsPressed: () {
|
||||
setState(() {
|
||||
_showUserDetails = !_showUserDetails;
|
||||
|
@ -134,12 +134,12 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
Theme.of(context).primaryColor, BlendMode.multiply)),
|
||||
),
|
||||
),
|
||||
new Builder(
|
||||
Builder(
|
||||
builder: (BuildContext context) => Expanded(
|
||||
child: _showUserDetails
|
||||
? _userDetailsWidget(context)
|
||||
: _namespacesWidget())),
|
||||
new Align(
|
||||
Align(
|
||||
alignment: FractionalOffset.bottomLeft,
|
||||
child: Builder(
|
||||
builder: (context) => ListTile(
|
||||
|
@ -151,7 +151,7 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
new Align(
|
||||
Align(
|
||||
alignment: FractionalOffset.bottomCenter,
|
||||
child: Builder(
|
||||
builder: (context) => ListTile(
|
||||
|
@ -197,9 +197,14 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
}
|
||||
|
||||
_addNamespace(String name, BuildContext context) {
|
||||
final currentUser = VikunjaGlobal.of(context).currentUser;
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VikunjaGlobal.of(context)
|
||||
.namespaceService
|
||||
.create(Namespace(id: 0, title: name))
|
||||
.create(Namespace(title: name, owner: currentUser))
|
||||
.then((_) {
|
||||
_loadNamespaces();
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
|
|
|
@ -103,20 +103,33 @@ class LandingPageState extends State<LandingPage> with AfterLayoutMixin<LandingP
|
|||
context: context,
|
||||
builder: (_) =>
|
||||
AddDialog(
|
||||
onAddTask: (task) => _addTask(task, context),
|
||||
onAddTask: (title, dueDate) => _addTask(title, dueDate, context),
|
||||
decoration: new InputDecoration(
|
||||
labelText: 'Task Name', hintText: 'eg. Milk')));
|
||||
}
|
||||
}
|
||||
|
||||
_addTask(Task task, BuildContext context) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
globalState.taskService.add(defaultList!, task).then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The task was added successfully!'),
|
||||
));
|
||||
_loadList(context).then((value) => setState((){}));
|
||||
});
|
||||
Future<void> _addTask(
|
||||
String title, DateTime? dueDate, BuildContext context) async {
|
||||
final globalState = VikunjaGlobal.of(context);
|
||||
if (globalState.currentUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await globalState.taskService.add(
|
||||
defaultList!,
|
||||
Task(
|
||||
title: title,
|
||||
dueDate: dueDate,
|
||||
createdBy: globalState.currentUser!,
|
||||
listId: defaultList!,
|
||||
),
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The task was added successfully!'),
|
||||
));
|
||||
_loadList(context).then((value) => setState(() {}));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class _ListPageState extends State<ListPage> {
|
|||
List<Task> _loadingTasks = [];
|
||||
int _currentPage = 1;
|
||||
bool _loading = true;
|
||||
bool? displayDoneTasks;
|
||||
bool displayDoneTasks = false;
|
||||
ListProvider? taskState;
|
||||
PageController? _pageController;
|
||||
Map<int, BucketProps> _bucketProps = {};
|
||||
|
@ -159,7 +159,7 @@ class _ListPageState extends State<ListPage> {
|
|||
ListView _listView(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
itemCount: taskState!.tasks.length,
|
||||
itemCount: taskState!.tasks.length * 2,
|
||||
itemBuilder: (context, i) {
|
||||
if (i.isOdd) return Divider();
|
||||
|
||||
|
@ -170,11 +170,6 @@ class _ListPageState extends State<ListPage> {
|
|||
|
||||
final index = i ~/ 2;
|
||||
|
||||
|
||||
// This handles the case if there are no more elements in the list left which can be provided by the api
|
||||
|
||||
// TODO
|
||||
// should never happen due to itemCount
|
||||
if (taskState!.maxPages == _currentPage &&
|
||||
index == taskState!.tasks.length)
|
||||
throw Exception("Check itemCount attribute");
|
||||
|
@ -341,7 +336,7 @@ class _ListPageState extends State<ListPage> {
|
|||
});
|
||||
});
|
||||
if (_bucketProps[bucket.id]!.titleController.text.isEmpty)
|
||||
_bucketProps[bucket.id]!.titleController.text = bucket.title ?? "";
|
||||
_bucketProps[bucket.id]!.titleController.text = bucket.title;
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
|
@ -389,7 +384,7 @@ class _ListPageState extends State<ListPage> {
|
|||
padding: const EdgeInsets.only(right: 2),
|
||||
child: Text(
|
||||
'${bucket.tasks.length}/${bucket.limit}',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
style: (theme.textTheme.titleMedium ?? TextStyle(fontSize: 16)).copyWith(
|
||||
color: bucket.limit != 0 && bucket.tasks.length >= bucket.limit
|
||||
? Colors.red : null,
|
||||
),
|
||||
|
@ -426,7 +421,7 @@ class _ListPageState extends State<ListPage> {
|
|||
}
|
||||
},
|
||||
itemBuilder: (context) {
|
||||
final bool enableDelete = (taskState?.buckets.length ?? 0) > 1;
|
||||
final bool enableDelete = taskState!.buckets.length > 1;
|
||||
return <PopupMenuEntry<BucketMenu>>[
|
||||
PopupMenuItem<BucketMenu>(
|
||||
value: BucketMenu.limit,
|
||||
|
@ -546,7 +541,7 @@ class _ListPageState extends State<ListPage> {
|
|||
newBucketId: bucket.id,
|
||||
index: 0,
|
||||
).then((_) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('${data.task.title} was moved to ${bucket.title} successfully!'),
|
||||
content: Text('\'${data.task.title}\' was moved to \'${bucket.title}\' successfully!'),
|
||||
)));
|
||||
setState(() => _bucketProps[bucket.id]!.taskDropSize = null);
|
||||
},
|
||||
|
@ -613,7 +608,7 @@ class _ListPageState extends State<ListPage> {
|
|||
context: context,
|
||||
listId: _list!.id,
|
||||
page: page,
|
||||
displayDoneTasks: displayDoneTasks ?? false
|
||||
displayDoneTasks: displayDoneTasks
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -631,22 +626,25 @@ class _ListPageState extends State<ListPage> {
|
|||
builder: (_) => AddDialog(
|
||||
onAdd: (title) => _addItem(title, context, bucket),
|
||||
decoration: InputDecoration(
|
||||
labelText: (bucket != null ? '${bucket.title}: ' : '') + 'New Task Name',
|
||||
labelText: (bucket != null ? '\'${bucket.title}\': ' : '') + 'New Task Name',
|
||||
hintText: 'eg. Milk',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addItem(String title, BuildContext context, [Bucket? bucket]) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
var newTask = Task(
|
||||
id: null,
|
||||
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: globalState.currentUser,
|
||||
createdBy: currentUser,
|
||||
done: false,
|
||||
bucketId: bucket?.id,
|
||||
identifier: '',
|
||||
listId: _list!.id,
|
||||
);
|
||||
setState(() => _loadingTasks.add(newTask));
|
||||
return Provider.of<ListProvider>(context, listen: false)
|
||||
|
@ -657,7 +655,7 @@ class _ListPageState extends State<ListPage> {
|
|||
)
|
||||
.then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The task was added successfully' + (bucket != null ? ' to ${bucket.title}' : '') + '!'),
|
||||
content: Text('The task was added successfully' + (bucket != null ? ' to \'${bucket.title}\'' : '') + '!'),
|
||||
));
|
||||
setState(() {
|
||||
_loadingTasks.remove(newTask);
|
||||
|
@ -679,23 +677,27 @@ class _ListPageState extends State<ListPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _addBucket(String title, BuildContext context) {
|
||||
return Provider.of<ListProvider>(context, listen: false).addBucket(
|
||||
Future<void> _addBucket(String title, BuildContext context) async {
|
||||
final currentUser = VikunjaGlobal.of(context).currentUser;
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Provider.of<ListProvider>(context, listen: false).addBucket(
|
||||
context: context,
|
||||
newBucket: Bucket(
|
||||
id: 0,
|
||||
title: title,
|
||||
createdBy: VikunjaGlobal.of(context).currentUser,
|
||||
createdBy: currentUser,
|
||||
listId: _list!.id,
|
||||
limit: 0,
|
||||
),
|
||||
listId: _list!.id,
|
||||
).then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The bucket was added successfully!'),
|
||||
));
|
||||
setState(() {});
|
||||
});
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The bucket was added successfully!'),
|
||||
));
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _updateBucket(BuildContext context, Bucket bucket) {
|
||||
|
@ -704,7 +706,7 @@ class _ListPageState extends State<ListPage> {
|
|||
bucket: bucket,
|
||||
).then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('${bucket.title} bucket updated successfully!'),
|
||||
content: Text('\'${bucket.title}\' bucket updated successfully!'),
|
||||
));
|
||||
setState(() {});
|
||||
});
|
||||
|
@ -715,16 +717,17 @@ class _ListPageState extends State<ListPage> {
|
|||
context: context,
|
||||
listId: bucket.listId,
|
||||
bucketId: bucket.id,
|
||||
).then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Row(
|
||||
children: <Widget>[
|
||||
Text('${bucket.title} was deleted.'),
|
||||
Icon(Icons.delete),
|
||||
],
|
||||
),
|
||||
));
|
||||
});
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Row(
|
||||
children: <Widget>[
|
||||
Text('\'${bucket.title}\' was deleted.'),
|
||||
Icon(Icons.delete),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
_onViewTapped(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ class ListEditPage extends StatefulWidget {
|
|||
class _ListEditPageState extends State<ListEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _loading = false;
|
||||
String? _title, _description;
|
||||
String _title = '', _description = '';
|
||||
bool? displayDoneTasks;
|
||||
int listId = -1;
|
||||
late int listId;
|
||||
|
||||
@override
|
||||
void initState(){
|
||||
|
@ -52,7 +52,7 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
initialValue: widget.list.title,
|
||||
onSaved: (title) => _title = 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.';
|
||||
|
@ -71,7 +71,7 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
initialValue: widget.list.description,
|
||||
onSaved: (description) => _description = description,
|
||||
onSaved: (description) => _description = description ?? '',
|
||||
validator: (description) {
|
||||
if(description == null)
|
||||
return null;
|
||||
|
@ -88,29 +88,16 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: displayDoneTasks != null ?
|
||||
CheckboxListTile(
|
||||
value: displayDoneTasks,
|
||||
title: Text("Show done tasks"),
|
||||
onChanged: (value) {
|
||||
VikunjaGlobal.of(context).listService.setDisplayDoneTasks(listId, value == false ? "0" : "1");
|
||||
setState(() => displayDoneTasks = value);
|
||||
},
|
||||
)
|
||||
: ListTile(
|
||||
trailing:
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
height: Checkbox.width,
|
||||
width: Checkbox.width,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
)
|
||||
),
|
||||
),
|
||||
title: Text("Show done task"),
|
||||
),),
|
||||
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),
|
||||
|
|
|
@ -32,41 +32,40 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
|
||||
int? _priority;
|
||||
DateTime? _dueDate, _startDate, _endDate;
|
||||
List<DateTime?>? _reminderDates;
|
||||
late final List<DateTime> _reminderDates;
|
||||
String? _title, _description, _repeatAfterType;
|
||||
Duration? _repeatAfter;
|
||||
List<Label>? _labels;
|
||||
late final List<Label> _labels;
|
||||
// we use this to find the label object after a user taps on the suggestion, because the typeahead only uses strings, not full objects.
|
||||
List<Label>? _suggestedLabels;
|
||||
var _reminderInputs = <Widget>[];
|
||||
final _reminderInputs = <Widget>[];
|
||||
final _labelTypeAheadController = TextEditingController();
|
||||
Color? _color;
|
||||
Color? _pickerColor;
|
||||
bool _resetColor = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_reminderDates = widget.task.reminderDates;
|
||||
|
||||
for (var i = 0; i < _reminderDates.length; i++) {
|
||||
_reminderInputs.add(VikunjaDateTimePicker(
|
||||
initialValue: _reminderDates[i],
|
||||
label: 'Reminder',
|
||||
onSaved: (reminder) {
|
||||
_reminderDates[i] = reminder ?? DateTime(0);
|
||||
return null;
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
_labels = widget.task.labels;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext ctx) {
|
||||
// This builds the initial list of reminder inputs only once.
|
||||
if (_reminderDates == null) {
|
||||
_reminderDates = [];
|
||||
widget.task.reminderDates?.forEach((element) { _reminderDates?.add(element ?? null);});
|
||||
|
||||
_reminderDates!.asMap().forEach((i, time) =>
|
||||
setState(() => _reminderInputs.add(VikunjaDateTimePicker(
|
||||
initialValue: time,
|
||||
label: 'Reminder',
|
||||
onSaved: (reminder) {
|
||||
_reminderDates![i] = reminder;
|
||||
return null;
|
||||
},
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
if (_labels == null) {
|
||||
_labels = widget.task.labels ?? [];
|
||||
}
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () {
|
||||
if(_changed) {
|
||||
|
@ -221,15 +220,15 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
),
|
||||
onTap: () {
|
||||
// We add a new entry every time we add a new input, to make sure all inputs have a place where they can put their value.
|
||||
_reminderDates!.add(null);
|
||||
var currentIndex = _reminderDates!.length - 1;
|
||||
_reminderDates.add(DateTime(0));
|
||||
var currentIndex = _reminderDates.length - 1;
|
||||
|
||||
// FIXME: Why does putting this into a row fails?
|
||||
setState(() => _reminderInputs.add(
|
||||
VikunjaDateTimePicker(
|
||||
label: 'Reminder',
|
||||
onSaved: (reminder) =>
|
||||
_reminderDates![currentIndex] = reminder,
|
||||
_reminderDates[currentIndex] = reminder ?? DateTime(0),
|
||||
onChanged: (_) => _changed = true,
|
||||
initialValue: DateTime.now(),
|
||||
),
|
||||
|
@ -262,7 +261,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
children: _labels!.map((Label label) {
|
||||
children: _labels.map((Label label) {
|
||||
return LabelComponent(
|
||||
label: label,
|
||||
onDelete: () {
|
||||
|
@ -312,9 +311,8 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
ElevatedButton(
|
||||
child: Text(
|
||||
'Color',
|
||||
style: _resetColor ? null : TextStyle(
|
||||
color: (_color ?? widget.task.color)
|
||||
.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
style: (_resetColor || (_color ?? widget.task.color) == null) ? null : TextStyle(
|
||||
color: (_color ?? widget.task.color)!.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
style: _resetColor ? null : ButtonStyle(
|
||||
|
@ -364,7 +362,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
setState(() => _loading = true);
|
||||
|
||||
// Removes all reminders with no value set.
|
||||
_reminderDates?.removeWhere((d) => d == null);
|
||||
_reminderDates.removeWhere((d) => d == DateTime(0));
|
||||
|
||||
Task updatedTask = widget.task.copyWith(
|
||||
title: _title,
|
||||
|
@ -417,7 +415,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
|
||||
_removeLabel(Label label) {
|
||||
setState(() {
|
||||
_labels?.removeWhere((l) => l.id == label.id);
|
||||
_labels.removeWhere((l) => l.id == label.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -425,7 +423,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
return VikunjaGlobal.of(context)
|
||||
.labelService.getAll(query: query).then((labels) {
|
||||
// Only show those labels which aren't already added to the task
|
||||
labels.removeWhere((labelToRemove) => _labels!.contains(labelToRemove));
|
||||
labels.removeWhere((labelToRemove) => _labels.contains(labelToRemove));
|
||||
_suggestedLabels = labels;
|
||||
List<String?> labelText = labels.map((label) => label.title).toList();
|
||||
return labelText;
|
||||
|
@ -437,7 +435,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
bool found = false;
|
||||
_suggestedLabels?.forEach((label) {
|
||||
if (label.title == labelTitle) {
|
||||
_labels?.add(label);
|
||||
_labels.add(label);
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
|
@ -448,19 +446,27 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
_createAndAddLabel(String labelTitle) {
|
||||
void _createAndAddLabel(String labelTitle) {
|
||||
// Only add a label if there are none to add
|
||||
if (labelTitle.isEmpty || (_suggestedLabels?.isNotEmpty ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Label newLabel = Label(title: labelTitle, id: 0);
|
||||
final currentUser = VikunjaGlobal.of(context).currentUser;
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final newLabel = Label(
|
||||
title: labelTitle,
|
||||
createdBy: currentUser,
|
||||
);
|
||||
VikunjaGlobal.of(context)
|
||||
.labelService
|
||||
.create(newLabel)
|
||||
.then((createdLabel) {
|
||||
setState(() {
|
||||
_labels?.add(createdLabel);
|
||||
_labels.add(createdLabel);
|
||||
_labelTypeAheadController.clear();
|
||||
});
|
||||
});
|
||||
|
@ -505,7 +511,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
}
|
||||
|
||||
_onColorEdit() {
|
||||
_pickerColor = _resetColor
|
||||
_pickerColor = _resetColor || (_color ?? widget.task.color) == null
|
||||
? Colors.black
|
||||
: _color ?? widget.task.color;
|
||||
showDialog(
|
||||
|
@ -524,11 +530,11 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
),
|
||||
actions: <TextButton>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text('CANCEL'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Reset'),
|
||||
child: Text('RESET'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_color = null;
|
||||
|
@ -539,7 +545,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Ok'),
|
||||
child: Text('OK'),
|
||||
onPressed: () {
|
||||
if (_pickerColor != Colors.black) setState(() {
|
||||
_color = _pickerColor;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
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';
|
||||
|
@ -48,7 +47,7 @@ class _NamespacePageState extends State<NamespacePage>
|
|||
key: Key(ls.id.toString()),
|
||||
direction: DismissDirection.startToEnd,
|
||||
child: ListTile(
|
||||
title: new Text(ls.title ?? ""),
|
||||
title: new Text(ls.title),
|
||||
onTap: () => _openList(context, ls),
|
||||
trailing: Icon(Icons.arrow_right),
|
||||
),
|
||||
|
@ -84,7 +83,7 @@ class _NamespacePageState extends State<NamespacePage>
|
|||
_loadLists();
|
||||
}
|
||||
|
||||
Future _removeList(TaskList list) {
|
||||
Future<void> _removeList(TaskList list) {
|
||||
return VikunjaGlobal.of(context)
|
||||
.listService
|
||||
.delete(list.id)
|
||||
|
@ -124,16 +123,22 @@ class _NamespacePageState extends State<NamespacePage>
|
|||
);
|
||||
}
|
||||
|
||||
_addList(String name, BuildContext context) {
|
||||
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(
|
||||
id: 0,
|
||||
title: name,
|
||||
tasks: [],
|
||||
namespaceId: widget.namespace.id))
|
||||
title: name,
|
||||
tasks: [],
|
||||
namespaceId: widget.namespace.id,
|
||||
owner: curentUser,
|
||||
))
|
||||
.then((_) {
|
||||
setState(() {});
|
||||
_loadLists();
|
||||
|
|
|
@ -16,7 +16,14 @@ class NamespaceEditPage extends StatefulWidget {
|
|||
class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _loading = false;
|
||||
String? _name, _description;
|
||||
late String _name, _description;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_name = widget.namespace.title;
|
||||
_description = widget.namespace.description;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext ctx) {
|
||||
|
@ -37,7 +44,7 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
initialValue: widget.namespace.title,
|
||||
onSaved: (name) => _name = name,
|
||||
onSaved: (name) => _name = name ?? '',
|
||||
validator: (name) {
|
||||
//if (name.length < 3 || name.length > 250) {
|
||||
// return 'The name needs to have between 3 and 250 characters.';
|
||||
|
@ -56,7 +63,7 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
initialValue: widget.namespace.description,
|
||||
onSaved: (description) => _description = description,
|
||||
onSaved: (description) => _description = description ?? '',
|
||||
validator: (description) {
|
||||
//if (description.length > 1000) {
|
||||
// return 'The description can have a maximum of 1000 characters.';
|
||||
|
@ -80,7 +87,7 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
_saveNamespace(context);
|
||||
}
|
||||
}
|
||||
: () => null,
|
||||
: null,
|
||||
child: _loading
|
||||
? CircularProgressIndicator()
|
||||
: VikunjaButtonText('Save'),
|
||||
|
@ -94,13 +101,10 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
|
||||
_saveNamespace(BuildContext context) async {
|
||||
setState(() => _loading = true);
|
||||
// FIXME: is there a way we can update the namespace without creating a new namespace object?
|
||||
// aka updating the existing namespace we got from context (setters?)
|
||||
Namespace updatedNamespace = Namespace(
|
||||
id: widget.namespace.id,
|
||||
title: _name,
|
||||
description: _description,
|
||||
owner: widget.namespace.owner);
|
||||
final updatedNamespace = widget.namespace.copyWith(
|
||||
title: _name,
|
||||
description: _description,
|
||||
);
|
||||
|
||||
VikunjaGlobal.of(context)
|
||||
.namespaceService
|
||||
|
|
|
@ -32,7 +32,7 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
ListTile(
|
||||
title: Text("Default List"),
|
||||
trailing: DropdownButton<int>(
|
||||
items: [DropdownMenuItem(child: Text("None"), value: null,), ...taskListList!.map((e) => DropdownMenuItem(child: Text(e.title ?? ""), value: e.id)).toList()],
|
||||
items: [DropdownMenuItem(child: Text("None"), value: null,), ...taskListList!.map((e) => DropdownMenuItem(child: Text(e.title), value: e.id)).toList()],
|
||||
value: defaultList,
|
||||
onChanged: (int? value){
|
||||
setState(() => defaultList = value);
|
||||
|
|
|
@ -33,19 +33,21 @@ class _LoginPageState extends State<LoginPage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(Duration.zero, () {
|
||||
if(VikunjaGlobal.of(context).expired) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Login has expired. Please reenter your details!")));
|
||||
setState(() {
|
||||
_serverController.text = VikunjaGlobal.of(context).client.base ?? "";
|
||||
_usernameController.text = VikunjaGlobal.of(context).currentUser?.username ?? "";
|
||||
});
|
||||
}
|
||||
});
|
||||
Future.delayed(Duration.zero, () {
|
||||
if(VikunjaGlobal.of(context).expired) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Login has expired. Please reenter your details!")));
|
||||
setState(() {
|
||||
_serverController.text = VikunjaGlobal.of(context).client.base;
|
||||
_usernameController.text = VikunjaGlobal.of(context).currentUser?.username ?? "";
|
||||
});
|
||||
}
|
||||
final client = VikunjaGlobal.of(context).client;
|
||||
VikunjaGlobal.of(context).settingsManager.getIgnoreCertificates().then((value) => setState(() => client.ignoreCertificates = value == "1"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -53,8 +55,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||
@override
|
||||
Widget build(BuildContext ctx) {
|
||||
Client client = VikunjaGlobal.of(context).client;
|
||||
if(client.ignoreCertificates == null)
|
||||
VikunjaGlobal.of(context).settingsManager.getIgnoreCertificates().then((value) => setState(() => client.ignoreCertificates = value == "1" ? true:false));
|
||||
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
|
@ -131,7 +131,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
_loginUser(context);
|
||||
}
|
||||
}
|
||||
: () => null,
|
||||
: null,
|
||||
child: _loading
|
||||
? CircularProgressIndicator()
|
||||
: VikunjaButtonText('Login'),
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:vikunja_app/models/user.dart';
|
|||
import 'package:vikunja_app/service/services.dart';
|
||||
|
||||
// Data for mocked services
|
||||
var _users = {1: User(1, 'test@testuser.org', 'test1')};
|
||||
var _users = {1: User(id: 1, username: 'test1')};
|
||||
|
||||
var _namespaces = {
|
||||
1: Namespace(
|
||||
|
@ -17,7 +17,7 @@ var _namespaces = {
|
|||
created: DateTime.now(),
|
||||
updated: DateTime.now(),
|
||||
description: 'A namespace for testing purposes',
|
||||
owner: _users[1],
|
||||
owner: _users[1]!,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -30,7 +30,7 @@ var _lists = {
|
|||
id: 1,
|
||||
title: 'List 1',
|
||||
tasks: _tasks.values.toList(),
|
||||
owner: _users[1],
|
||||
owner: _users[1]!,
|
||||
description: 'A nice list',
|
||||
created: DateTime.now(),
|
||||
updated: DateTime.now(),
|
||||
|
@ -41,12 +41,12 @@ var _tasks = {
|
|||
1: Task(
|
||||
id: 1,
|
||||
title: 'Task 1',
|
||||
createdBy: _users[1],
|
||||
createdBy: _users[1]!,
|
||||
updated: DateTime.now(),
|
||||
created: DateTime.now(),
|
||||
description: 'A descriptive task',
|
||||
done: false,
|
||||
identifier: '',
|
||||
listId: 1,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -146,7 +146,7 @@ class MockedTaskService implements TaskService {
|
|||
@override
|
||||
Future delete(int taskId) {
|
||||
_lists.forEach(
|
||||
(_, list) => list.tasks.removeWhere((task) => task?.id == taskId));
|
||||
(_, list) => list.tasks.removeWhere((task) => task.id == taskId));
|
||||
_tasks.remove(taskId);
|
||||
return Future.value();
|
||||
}
|
||||
|
@ -154,12 +154,12 @@ class MockedTaskService implements TaskService {
|
|||
@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);
|
||||
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 ?? 0] = task);
|
||||
return Future.value(_tasks[task.id] = task);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -87,14 +87,17 @@ class ListProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> addTaskByTitle(
|
||||
{required BuildContext context, required String title, required int listId}) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
var newTask = Task(
|
||||
id: 0,
|
||||
identifier: '',
|
||||
{required BuildContext context, required String title, required int listId}) async{
|
||||
final globalState = VikunjaGlobal.of(context);
|
||||
if (globalState.currentUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final newTask = Task(
|
||||
title: title,
|
||||
createdBy: globalState.currentUser,
|
||||
createdBy: globalState.currentUser!,
|
||||
done: false,
|
||||
listId: listId,
|
||||
);
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
@ -116,11 +119,7 @@ class ListProvider with ChangeNotifier {
|
|||
_tasks.insert(0, task);
|
||||
if (_buckets.isNotEmpty) {
|
||||
final bucket = _buckets[_buckets.indexWhere((b) => task.bucketId == b.id)];
|
||||
if (bucket.tasks != null) {
|
||||
bucket.tasks.add(task);
|
||||
} else {
|
||||
bucket.tasks = <Task>[task];
|
||||
}
|
||||
bucket.tasks.add(task);
|
||||
}
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
@ -159,7 +158,7 @@ class ListProvider with ChangeNotifier {
|
|||
return VikunjaGlobal.of(context).bucketService.update(bucket)
|
||||
.then((rBucket) {
|
||||
_buckets[_buckets.indexWhere((b) => rBucket.id == b.id)] = rBucket;
|
||||
_buckets.sort((a, b) => a.position.compareTo(b.position));
|
||||
_buckets.sort((a, b) => a.position!.compareTo(b.position!));
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
@ -172,7 +171,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> moveTaskToBucket({required BuildContext context, required Task task, required int newBucketId, required int index}) async {
|
||||
Future<void> moveTaskToBucket({required BuildContext context, required Task task, int? newBucketId, required int index}) async {
|
||||
final sameBucket = task.bucketId == newBucketId;
|
||||
final newBucketIndex = _buckets.indexWhere((b) => b.id == newBucketId);
|
||||
if (sameBucket && index > _buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task.id)) index--;
|
||||
|
@ -212,7 +211,7 @@ class ListProvider with ChangeNotifier {
|
|||
if (_tasks.isNotEmpty) {
|
||||
_tasks[_tasks.indexWhere((t) => t.id == task.id)] = task;
|
||||
if (secondTask != null)
|
||||
_tasks[_tasks.indexWhere((t) => t.id == secondTask?.id)] = secondTask;
|
||||
_tasks[_tasks.indexWhere((t) => t.id == secondTask!.id)] = secondTask;
|
||||
}
|
||||
|
||||
_buckets[newBucketIndex].tasks[_buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task.id)] = task;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'package:meta/meta.dart';
|
||||
|
||||
class CheckboxStatistics {
|
||||
final int total;
|
||||
final int checked;
|
||||
|
@ -28,7 +26,7 @@ MatchedCheckboxes getCheckboxesInText(String text) {
|
|||
final matches = RegExp(r'[*-] \[[ x]]').allMatches(text);
|
||||
|
||||
for (final match in matches) {
|
||||
if (match[0]!.endsWith(checkedString))
|
||||
if (match[0]?.endsWith(checkedString) ?? false)
|
||||
checked.add(match);
|
||||
else
|
||||
unchecked.add(match);
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
final RegExp _emailRegex = new RegExp(
|
||||
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
|
||||
|
||||
bool isEmail(email) {
|
||||
bool isEmail(String? email) {
|
||||
if (email == null) return false;
|
||||
return _emailRegex.hasMatch(email);
|
||||
}
|
||||
|
||||
final RegExp _url = new RegExp(
|
||||
r'https?:\/\/((([a-zA-Z0-9.\-\_]+)\.[a-zA-Z]+)|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(:[0-9]+)?');
|
||||
|
||||
bool isUrl(url) {
|
||||
bool isUrl(String? url) {
|
||||
if (url == null) return false;
|
||||
return _url.hasMatch(url);
|
||||
}
|
||||
|
|
51
pubspec.lock
51
pubspec.lock
|
@ -7,7 +7,7 @@ packages:
|
|||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "46.0.0"
|
||||
version: "47.0.0"
|
||||
after_layout:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -21,7 +21,7 @@ packages:
|
|||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.6.0"
|
||||
version: "4.7.0"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -42,7 +42,7 @@ packages:
|
|||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -70,14 +70,7 @@ packages:
|
|||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.2.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -98,7 +91,7 @@ packages:
|
|||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -119,7 +112,7 @@ packages:
|
|||
name: coverage
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.5.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -168,7 +161,7 @@ packages:
|
|||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -229,7 +222,7 @@ packages:
|
|||
name: flutter_local_notifications
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.8.0+1"
|
||||
version: "9.9.1"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -400,21 +393,21 @@ packages:
|
|||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.8.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -449,7 +442,7 @@ packages:
|
|||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -594,7 +587,7 @@ packages:
|
|||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -615,35 +608,35 @@ packages:
|
|||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.21.1"
|
||||
version: "1.21.4"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.12"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.13"
|
||||
version: "0.4.16"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -671,7 +664,7 @@ packages:
|
|||
name: vm_service
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
version: "9.3.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -692,7 +685,7 @@ packages:
|
|||
name: webkit_inspection_protocol
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.2.0"
|
||||
webview_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -706,14 +699,14 @@ packages:
|
|||
name: webview_flutter_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.9.5"
|
||||
version: "2.10.0"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.2"
|
||||
version: "1.9.3"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -32,7 +32,7 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
version: any
|
||||
test: 1.21.1
|
||||
test: ^1.21.1
|
||||
flutter_launcher_icons: ^0.10.0
|
||||
|
||||
flutter_icons:
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
|||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vikunja_app/models/label.dart';
|
||||
import 'package:vikunja_app/models/user.dart';
|
||||
|
||||
void main() {
|
||||
test('label color from json', () {
|
||||
|
@ -14,9 +15,9 @@ void main() {
|
|||
});
|
||||
|
||||
test('hex color string from object', () {
|
||||
Label label = Label(id: 1, color: Color(0xFFe8e8e8));
|
||||
Label label = Label(id: 1, title: '', color: Color(0xFFe8e8e8), createdBy: User(id: 0, username: ''));
|
||||
var json = label.toJSON();
|
||||
|
||||
expect(json.toString(), '{id: 1, title: null, description: null, hex_color: e8e8e8, created_by: null, updated: null, created: null}');
|
||||
expect(json.toString(), '{id: 1, title: , description: null, hex_color: e8e8e8, created_by: {id: 0, username: ,}, updated: null, created: null}');
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user