mirror of
https://github.com/go-vikunja/app
synced 2024-06-02 18:49:47 +00:00
first step of migration to null-safety
This commit is contained in:
parent
48067d6b34
commit
b5266020a8
|
@ -36,10 +36,10 @@ class Client {
|
|||
configure(token: token, base: base, authenticated: authenticated);
|
||||
}
|
||||
|
||||
void reload_ignore_certs(bool val) {
|
||||
ignoreCertificates = val;
|
||||
void reload_ignore_certs(bool? val) {
|
||||
ignoreCertificates = val ?? false;
|
||||
HttpOverrides.global = new IgnoreCertHttpOverrides(ignoreCertificates);
|
||||
VikunjaGlobal.of(global.currentContext!).settingsManager.setIgnoreCertificates(val);
|
||||
VikunjaGlobal.of(global.currentContext!).settingsManager.setIgnoreCertificates(ignoreCertificates);
|
||||
|
||||
}
|
||||
|
||||
|
@ -53,9 +53,12 @@ class Client {
|
|||
int get hashCode => _token.hashCode;
|
||||
|
||||
void configure({String? token, String? base, bool? authenticated}) {
|
||||
_token = token!;
|
||||
_base = base!.endsWith('/api/v1') ? base : '$base/api/v1';
|
||||
this.authenticated = authenticated!;
|
||||
if(token != null)
|
||||
_token = token;
|
||||
if(base != null)
|
||||
_base = base.endsWith('/api/v1') ? base : '$base/api/v1';
|
||||
if(authenticated != null)
|
||||
this.authenticated = authenticated;
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,7 +89,7 @@ class Client {
|
|||
Future<Response> delete(String url) {
|
||||
return http
|
||||
.delete(
|
||||
'${this.base}$url'.toUri(),
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
)
|
||||
.then(_handleResponse, onError: _handleError);
|
||||
|
@ -96,7 +99,7 @@ class Client {
|
|||
log('post');
|
||||
return http
|
||||
.post(
|
||||
'${this.base}$url'.toUri(),
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
body: _encoder.convert(body),
|
||||
)
|
||||
|
@ -106,7 +109,7 @@ class Client {
|
|||
Future<Response> put(String url, {dynamic body}) {
|
||||
return http
|
||||
.put(
|
||||
'${this.base}$url'.toUri(),
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
body: _encoder.convert(body),
|
||||
)
|
||||
|
|
|
@ -10,14 +10,14 @@ class LabelTaskAPIService extends APIService implements LabelTaskService {
|
|||
@override
|
||||
Future<Label> create(LabelTask lt) async {
|
||||
return client
|
||||
.put('/tasks/${lt.task.id}/labels', body: lt.toJSON())
|
||||
.put('/tasks/${lt.task!.id}/labels', body: lt.toJSON())
|
||||
.then((result) => Label.fromJson(result.body));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Label> delete(LabelTask lt) async {
|
||||
return client
|
||||
.delete('/tasks/${lt.task.id}/labels/${lt.label.id}')
|
||||
.delete('/tasks/${lt.task!.id}/labels/${lt.label.id}')
|
||||
.then((result) => Label.fromJson(result.body));
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ class LabelTaskAPIService extends APIService implements LabelTaskService {
|
|||
String? params =
|
||||
query == null ? null : '?s=' + Uri.encodeQueryComponent(query);
|
||||
|
||||
return client.get('/tasks/${lt.task.id}/labels$params').then(
|
||||
return client.get('/tasks/${lt.task!.id}/labels$params').then(
|
||||
(label) => convertList(label, (result) => Label.fromJson(result)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ class LabelTaskBulkAPIService extends APIService
|
|||
LabelTaskBulkAPIService(Client client) : super(client);
|
||||
|
||||
@override
|
||||
Future<List<Label>> update(Task task, List<Label> labels) {
|
||||
Future<List<Label>> update(Task task, List<Label>? labels) {
|
||||
if(labels == null)
|
||||
labels = [];
|
||||
return client
|
||||
.post('/tasks/${task.id}/labels/bulk',
|
||||
body: LabelTaskBulk(labels: labels).toJSON())
|
||||
|
|
|
@ -88,7 +88,7 @@ class ListAPIService extends APIService implements ListService {
|
|||
}
|
||||
|
||||
@override
|
||||
void setDefaultList(int listId) {
|
||||
void setDefaultList(int? listId) {
|
||||
_storage.write(key: "default_list_id", value: listId.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,11 +65,11 @@ 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: null,
|
||||
widget.onAddTask!(Task(id: 0,
|
||||
title: textController.text,
|
||||
done: false,
|
||||
createdBy: null,
|
||||
dueDate: customDueDate));
|
||||
dueDate: customDueDate, identifier: ''));
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
|
@ -82,7 +82,7 @@ class AddDialogState extends State<AddDialog> {
|
|||
return Row(children: [
|
||||
Checkbox(value: newTaskDue == thisNewTaskDue, onChanged: (value) {
|
||||
newTaskDue = thisNewTaskDue;
|
||||
setState(() => customDueDate = DateTime.now().add(newTaskDueToDuration[thisNewTaskDue]));}, shape: CircleBorder(),),
|
||||
setState(() => customDueDate = DateTime.now().add(newTaskDueToDuration[thisNewTaskDue]!));}, shape: CircleBorder(),),
|
||||
Text(name),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:vikunja_app/models/bucket.dart';
|
|||
class BucketLimitDialog extends StatefulWidget {
|
||||
final Bucket bucket;
|
||||
|
||||
const BucketLimitDialog({Key key, @required this.bucket}) : super(key: key);
|
||||
const BucketLimitDialog({Key? key, required this.bucket}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BucketLimitDialog> createState() => _BucketLimitDialogState();
|
||||
|
|
|
@ -25,11 +25,11 @@ class BucketTaskCard extends StatefulWidget {
|
|||
final void Function(Task, int) onAccept;
|
||||
|
||||
const BucketTaskCard({
|
||||
Key key,
|
||||
@required this.task,
|
||||
@required this.index,
|
||||
@required this.onDragUpdate,
|
||||
@required this.onAccept,
|
||||
Key? key,
|
||||
required this.task,
|
||||
required this.index,
|
||||
required this.onDragUpdate,
|
||||
required this.onAccept,
|
||||
}) : assert(task != null),
|
||||
assert(index != null),
|
||||
assert(onDragUpdate != null),
|
||||
|
@ -41,10 +41,10 @@ class BucketTaskCard extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAliveClientMixin {
|
||||
Size _cardSize;
|
||||
Size? _cardSize;
|
||||
bool _dragging = false;
|
||||
DropLocation _dropLocation = DropLocation.none;
|
||||
TaskData _dropData;
|
||||
TaskData? _dropData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -63,20 +63,20 @@ 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?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
if (widget.task.done) {
|
||||
if (widget.task.done ?? false) {
|
||||
identifierRow.children.insert(0, Container(
|
||||
constraints: chipConstraints,
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: FittedBox(
|
||||
child: Chip(
|
||||
label: Text('Done'),
|
||||
labelStyle: theme.textTheme.labelLarge.copyWith(
|
||||
labelStyle: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.brightness == Brightness.dark
|
||||
? Colors.black : Colors.white,
|
||||
|
@ -91,8 +91,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?.copyWith(
|
||||
color: widget.task.textColor,
|
||||
),
|
||||
),
|
||||
|
@ -100,7 +100,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
],
|
||||
);
|
||||
if (widget.task.hasDueDate) {
|
||||
final duration = widget.task.dueDate.difference(DateTime.now());
|
||||
final duration = widget.task.dueDate!.difference(DateTime.now());
|
||||
final pastDue = duration.isNegative && !widget.task.done;
|
||||
titleRow.children.add(Container(
|
||||
constraints: chipConstraints,
|
||||
|
@ -112,7 +112,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?.copyWith(
|
||||
color: pastDue ? Colors.red : null,
|
||||
),
|
||||
backgroundColor: pastDue ? Colors.red.withAlpha(20) : null,
|
||||
|
@ -126,19 +126,19 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
);
|
||||
widget.task.labels?.sort((a, b) => a.title.compareTo(b.title));
|
||||
widget.task.labels?.asMap()?.forEach((i, label) {
|
||||
widget.task.labels?.sort((a, b) => a.title?.compareTo(b.title ?? "") ?? 0);
|
||||
widget.task.labels?.asMap().forEach((i, label) {
|
||||
labelRow.children.add(Chip(
|
||||
label: Text(label.title),
|
||||
labelStyle: theme.textTheme.labelLarge.copyWith(
|
||||
label: Text(label.title ?? ""),
|
||||
labelStyle: theme.textTheme.labelLarge?.copyWith(
|
||||
color: label.textColor,
|
||||
),
|
||||
backgroundColor: label.color,
|
||||
));
|
||||
});
|
||||
if (widget.task.hasCheckboxes) {
|
||||
final checkboxStatistics = widget.task.checkboxStatistics;
|
||||
final iconSize = (theme.textTheme.labelLarge.fontSize ?? 14) + 2;
|
||||
final checkboxStatistics = widget.task.checkboxStatistics!;
|
||||
final iconSize = (theme.textTheme.labelLarge?.fontSize ?? 14) + 2;
|
||||
labelRow.children.add(Chip(
|
||||
avatar: Container(
|
||||
constraints: BoxConstraints(maxHeight: iconSize, maxWidth: iconSize),
|
||||
|
@ -153,7 +153,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
),
|
||||
));
|
||||
}
|
||||
if (widget.task.attachments != null && widget.task.attachments.isNotEmpty) {
|
||||
if (widget.task.attachments != null && widget.task.attachments!.isNotEmpty) {
|
||||
labelRow.children.add(Chip(
|
||||
label: Transform.rotate(
|
||||
angle: -pi / 4.0,
|
||||
|
@ -161,7 +161,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
),
|
||||
));
|
||||
}
|
||||
if (widget.task.description.isNotEmpty) {
|
||||
if (widget.task.description != null && widget.task.description!.isNotEmpty) {
|
||||
labelRow.children.add(Chip(
|
||||
label: Icon(Icons.notes),
|
||||
));
|
||||
|
@ -214,7 +214,7 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
);
|
||||
|
||||
return LongPressDraggable<TaskData>(
|
||||
data: TaskData(widget.task, _cardSize),
|
||||
data: TaskData(widget.task, _cardSize!),
|
||||
maxSimultaneousDrags: taskState.taskDragging ? 0 : 1, // only one task can be dragged at a time
|
||||
onDragStarted: () {
|
||||
taskState.taskDragging = true;
|
||||
|
@ -268,8 +268,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,18 +282,18 @@ 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),
|
||||
onWillAccept: (data) => dragTargetOnWillAccept(data!, DropLocation.above),
|
||||
onAccept: dragTargetOnAccept,
|
||||
onLeave: dragTargetOnLeave,
|
||||
builder: (_, __, ___) => SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
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),
|
||||
onWillAccept: (data) => dragTargetOnWillAccept(data!, DropLocation.below),
|
||||
onAccept: dragTargetOnAccept,
|
||||
onLeave: dragTargetOnLeave,
|
||||
builder: (_, __, ___) => SizedBox.expand(),
|
||||
|
|
|
@ -10,12 +10,10 @@ class SliverBucketList extends StatelessWidget {
|
|||
final DragUpdateCallback onTaskDragUpdate;
|
||||
|
||||
const SliverBucketList({
|
||||
Key key,
|
||||
@required this.bucket,
|
||||
@required this.onTaskDragUpdate,
|
||||
}) : assert(bucket != null),
|
||||
assert(onTaskDragUpdate != null),
|
||||
super(key: key);
|
||||
Key? key,
|
||||
required this.bucket,
|
||||
required this.onTaskDragUpdate,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -6,8 +6,8 @@ class SliverBucketPersistentHeader extends StatelessWidget {
|
|||
final double maxExtent;
|
||||
|
||||
const SliverBucketPersistentHeader({
|
||||
Key key,
|
||||
@required this.child,
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.minExtent = 10.0,
|
||||
this.maxExtent = 10.0,
|
||||
}) : super(key: key);
|
||||
|
|
|
@ -12,12 +12,11 @@ class TaskTile extends StatefulWidget {
|
|||
final Function onEdit;
|
||||
final bool showInfo;
|
||||
final bool loading;
|
||||
final ValueSetter<bool> onMarkedAsDone;
|
||||
final ValueSetter<bool>? onMarkedAsDone;
|
||||
|
||||
const TaskTile(
|
||||
{Key key, @required this.task, this.onEdit, this.loading = false, this.showInfo = false, this.onMarkedAsDone})
|
||||
: assert(task != null),
|
||||
super(key: key);
|
||||
{Key? key, required this.task, required this.onEdit, this.loading = false, this.showInfo = false, this.onMarkedAsDone})
|
||||
: super(key: key);
|
||||
/*
|
||||
@override
|
||||
TaskTileState createState() {
|
||||
|
@ -32,13 +31,12 @@ class TaskTile extends StatefulWidget {
|
|||
class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
||||
Task _currentTask;
|
||||
|
||||
TaskTileState(this._currentTask)
|
||||
: assert(_currentTask != null);
|
||||
TaskTileState(this._currentTask);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
Duration durationUntilDue = _currentTask.dueDate.difference(DateTime.now());
|
||||
Duration? durationUntilDue = _currentTask.dueDate?.difference(DateTime.now());
|
||||
if (_currentTask.loading) {
|
||||
return ListTile(
|
||||
leading: Padding(
|
||||
|
@ -50,11 +48,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 == null || _currentTask.description!.isEmpty
|
||||
? null
|
||||
: Text(_currentTask.description),
|
||||
: Text(_currentTask.description ?? ""),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.settings), onPressed: () { },
|
||||
),
|
||||
|
@ -74,14 +72,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 ?? false,
|
||||
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: TextStyle(color: durationUntilDue.isNegative ? Colors.red : null),)
|
||||
: _currentTask.description == null || _currentTask.description!.isEmpty
|
||||
? null
|
||||
: Text(_currentTask.description),
|
||||
: Text(_currentTask.description ?? ""),
|
||||
secondary:
|
||||
IconButton(icon: Icon(Icons.settings), onPressed: () {
|
||||
Navigator.push<Task>(
|
||||
|
@ -100,7 +98,8 @@ class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
|||
);
|
||||
}
|
||||
|
||||
void _change(bool value) async {
|
||||
void _change(bool? value) async {
|
||||
value = value ?? false;
|
||||
setState(() {
|
||||
this._currentTask.loading = true;
|
||||
});
|
||||
|
|
|
@ -5,16 +5,16 @@ import 'package:vikunja_app/theme/constants.dart';
|
|||
|
||||
class VikunjaDateTimePicker extends StatelessWidget {
|
||||
final String label;
|
||||
final Function onSaved;
|
||||
final Function onChanged;
|
||||
final DateTime initialValue;
|
||||
final void Function(DateTime?)? onSaved;
|
||||
final void Function(DateTime?)? onChanged;
|
||||
final DateTime? initialValue;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Icon icon;
|
||||
final InputBorder border;
|
||||
|
||||
const VikunjaDateTimePicker({
|
||||
Key key,
|
||||
@required this.label,
|
||||
Key? key,
|
||||
required this.label,
|
||||
this.onSaved,
|
||||
this.onChanged,
|
||||
this.initialValue,
|
||||
|
@ -28,9 +28,9 @@ class VikunjaDateTimePicker extends StatelessWidget {
|
|||
return DateTimeField(
|
||||
//dateOnly: false,
|
||||
//editable: false, // Otherwise editing the date is not possible, this setting affects the underlying text field.
|
||||
initialValue: initialValue == null || initialValue.year <= 1
|
||||
initialValue: initialValue == null || initialValue!.year <= 1
|
||||
? null
|
||||
: initialValue.toLocal(),
|
||||
: initialValue!.toLocal(),
|
||||
format: vDateFormatLong,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
|
@ -47,7 +47,7 @@ class VikunjaDateTimePicker extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Future<DateTime> _showDatePickerFuture(context, currentValue) {
|
||||
Future<DateTime?> _showDatePickerFuture(context, currentValue) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (_) => DatePickerDialog(
|
||||
|
|
|
@ -6,7 +6,7 @@ class LabelComponent extends StatefulWidget {
|
|||
final Label label;
|
||||
final VoidCallback onDelete;
|
||||
|
||||
const LabelComponent({Key key, @required this.label, this.onDelete})
|
||||
const LabelComponent({Key? key, required this.label, required this.onDelete})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -24,7 +24,7 @@ class LabelComponentState extends State<LabelComponent> {
|
|||
|
||||
return Chip(
|
||||
label: Text(
|
||||
widget.label.title,
|
||||
widget.label.title ?? "",
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
),
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
extension StringExtensions on String {
|
||||
Uri toUri() => Uri.tryParse(this);
|
||||
Uri? toUri() => Uri.tryParse(this);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class VikunjaGlobal extends StatefulWidget {
|
|||
final Widget child;
|
||||
final Widget login;
|
||||
|
||||
VikunjaGlobal({this.child, this.login});
|
||||
VikunjaGlobal({required this.child, required this.login});
|
||||
|
||||
@override
|
||||
VikunjaGlobalState createState() => VikunjaGlobalState();
|
||||
|
@ -35,21 +35,21 @@ class VikunjaGlobal extends StatefulWidget {
|
|||
static VikunjaGlobalState of(BuildContext context) {
|
||||
var widget =
|
||||
context.dependOnInheritedWidgetOfExactType<_VikunjaGlobalInherited>();
|
||||
return widget.data;
|
||||
return widget!.data;
|
||||
}
|
||||
}
|
||||
|
||||
class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||
final FlutterSecureStorage _storage = new FlutterSecureStorage();
|
||||
|
||||
User _currentUser;
|
||||
User? _currentUser;
|
||||
bool _loading = true;
|
||||
bool expired = false;
|
||||
Client _client;
|
||||
UserService _newUserService;
|
||||
late Client _client;
|
||||
UserService? _newUserService;
|
||||
|
||||
|
||||
User get currentUser => _currentUser;
|
||||
User? get currentUser => _currentUser;
|
||||
|
||||
Client get client => _client;
|
||||
|
||||
|
@ -58,7 +58,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
|
||||
UserManager get userManager => new UserManager(_storage);
|
||||
|
||||
UserService get newUserService => _newUserService;
|
||||
UserService? get newUserService => _newUserService;
|
||||
|
||||
ServerService get serverService => new ServerAPIService(client);
|
||||
|
||||
|
@ -78,7 +78,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
|
||||
NotificationClass get notifications => new NotificationClass();
|
||||
|
||||
notifs.NotificationAppLaunchDetails notifLaunch;
|
||||
notifs.NotificationAppLaunchDetails? notifLaunch;
|
||||
|
||||
LabelService get labelService => new LabelAPIService(client);
|
||||
|
||||
|
@ -101,11 +101,11 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
icon: 'ic_launcher_foreground',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
notifs.IOSNotificationDetails iOSSpecifics;
|
||||
notifs.NotificationDetails platformChannelSpecificsDueDate;
|
||||
notifs.NotificationDetails platformChannelSpecificsReminders;
|
||||
late notifs.IOSNotificationDetails iOSSpecifics;
|
||||
late notifs.NotificationDetails platformChannelSpecificsDueDate;
|
||||
late notifs.NotificationDetails platformChannelSpecificsReminders;
|
||||
|
||||
String currentTimeZone;
|
||||
late String currentTimeZone;
|
||||
|
||||
|
||||
|
||||
|
@ -126,7 +126,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
|
||||
}
|
||||
|
||||
void changeUser(User newUser, {String token, String base}) async {
|
||||
void changeUser(User newUser, {String? token, String? base}) async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
|
@ -163,20 +163,20 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
taskService.getAll().then((value) =>
|
||||
value.forEach((task) {
|
||||
if(task.reminderDates != null)
|
||||
task.reminderDates.forEach((reminder) {
|
||||
scheduleNotification("Reminder", "This is your reminder for '" + task.title + "'",
|
||||
task.reminderDates!.forEach((reminder) {
|
||||
scheduleNotification("Reminder", "This is your reminder for '" + task.title! + "'",
|
||||
notificationsPlugin,
|
||||
reminder,
|
||||
reminder!,
|
||||
currentTimeZone,
|
||||
platformChannelSpecifics: platformChannelSpecificsReminders,
|
||||
platformChannelSpecificsReminders,
|
||||
id: (reminder.millisecondsSinceEpoch/1000).floor());
|
||||
});
|
||||
if(task.dueDate != null)
|
||||
scheduleNotification("Due Reminder","The task '" + task.title + "' is due.",
|
||||
scheduleNotification("Due Reminder","The task '" + task.title! + "' is due.",
|
||||
notificationsPlugin,
|
||||
task.dueDate,
|
||||
task.dueDate!,
|
||||
currentTimeZone,
|
||||
platformChannelSpecifics: platformChannelSpecificsDueDate,
|
||||
platformChannelSpecificsDueDate,
|
||||
id: task.id);
|
||||
})
|
||||
);
|
||||
|
@ -233,9 +233,9 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
});
|
||||
return;
|
||||
}
|
||||
loadedCurrentUser = User(int.tryParse(currentUser), "", "");
|
||||
loadedCurrentUser = User(int.tryParse(currentUser)!, "", "");
|
||||
} catch (otherExceptions) {
|
||||
loadedCurrentUser = User(int.tryParse(currentUser), "", "");
|
||||
loadedCurrentUser = User(int.tryParse(currentUser)!, "", "");
|
||||
}
|
||||
setState(() {
|
||||
_currentUser = loadedCurrentUser;
|
||||
|
@ -248,12 +248,13 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
if (_loading) {
|
||||
return new Center(child: new CircularProgressIndicator());
|
||||
}
|
||||
if(client != null && client.authenticated) {
|
||||
if(client.authenticated) {
|
||||
scheduleDueNotifications();
|
||||
}
|
||||
return new _VikunjaGlobalInherited(
|
||||
data: this,
|
||||
child: client == null || !client.authenticated ? widget.login : widget.child,
|
||||
key: UniqueKey(),
|
||||
child: !client.authenticated ? widget.login : widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -261,13 +262,13 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
class _VikunjaGlobalInherited extends InheritedWidget {
|
||||
final VikunjaGlobalState data;
|
||||
|
||||
_VikunjaGlobalInherited({Key key, this.data, Widget child})
|
||||
_VikunjaGlobalInherited({Key? key, required this.data, required Widget child})
|
||||
: super(key: key, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_VikunjaGlobalInherited oldWidget) {
|
||||
return (data.currentUser != null &&
|
||||
data.currentUser.id != oldWidget.data.currentUser.id) ||
|
||||
data.currentUser!.id != oldWidget.data.currentUser!.id) ||
|
||||
data.client != oldWidget.data.client;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import 'package:vikunja_app/theme/theme.dart';
|
|||
import 'package:http/http.dart';
|
||||
|
||||
class IgnoreCertHttpOverrides extends HttpOverrides {
|
||||
bool ignoreCerts;
|
||||
bool ignoreCerts = false;
|
||||
IgnoreCertHttpOverrides(bool _ignore) {ignoreCerts = _ignore;}
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext context) {
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback = (_, __, ___) => ignoreCerts;
|
||||
}
|
||||
|
@ -19,15 +19,15 @@ class IgnoreCertHttpOverrides extends HttpOverrides {
|
|||
|
||||
void main() {
|
||||
runApp(VikunjaGlobal(
|
||||
child: new VikunjaApp(home: HomePage()),
|
||||
login: new VikunjaApp(home: LoginPage())));
|
||||
child: new VikunjaApp(home: HomePage(), key: UniqueKey(),),
|
||||
login: new VikunjaApp(home: LoginPage(), key: UniqueKey(),)));
|
||||
}
|
||||
|
||||
|
||||
class VikunjaApp extends StatelessWidget {
|
||||
final Widget home;
|
||||
|
||||
const VikunjaApp({Key key, this.home}) : super(key: key);
|
||||
const VikunjaApp({Key? key, required this.home}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@ import 'package:rxdart/subjects.dart' as rxSub;
|
|||
import 'package:vikunja_app/global.dart';
|
||||
|
||||
class NotificationClass{
|
||||
final int id;
|
||||
final String title;
|
||||
final String body;
|
||||
final String payload;
|
||||
final int? id;
|
||||
final String? title;
|
||||
final String? body;
|
||||
final String? payload;
|
||||
NotificationClass({this.id, this.body, this.payload, this.title});
|
||||
|
||||
final rxSub.BehaviorSubject<NotificationClass> didReceiveLocalNotificationSubject =
|
||||
|
@ -32,18 +32,18 @@ class NotificationClass{
|
|||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
onDidReceiveLocalNotification:
|
||||
(int id, String title, String body, String payload) async {
|
||||
(int? id, String? title, String? body, String? payload) async {
|
||||
didReceiveLocalNotificationSubject
|
||||
.add(NotificationClass(id: id, title: title, body: body, payload: payload));
|
||||
});
|
||||
var initializationSettings = notifs.InitializationSettings(
|
||||
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
|
||||
await notifsPlugin.initialize(initializationSettings,
|
||||
onSelectNotification: (String payload) async {
|
||||
onSelectNotification: (String? payload) async {
|
||||
if (payload != null) {
|
||||
print('notification payload: ' + payload);
|
||||
selectNotificationSubject.add(payload);
|
||||
}
|
||||
selectNotificationSubject.add(payload);
|
||||
});
|
||||
print("Notifications initialised successfully");
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class NotificationClass{
|
|||
|
||||
Future<void> scheduleNotification(String title, String description,
|
||||
notifs.FlutterLocalNotificationsPlugin notifsPlugin,
|
||||
DateTime scheduledTime, String currentTimeZone, {int id, notifs.NotificationDetails platformChannelSpecifics}) async {
|
||||
DateTime scheduledTime, String currentTimeZone, notifs.NotificationDetails platformChannelSpecifics, {int? id}) async {
|
||||
if(id == null)
|
||||
id = Random().nextInt(1000000);
|
||||
// TODO: move to setup
|
||||
|
|
|
@ -7,7 +7,7 @@ class UserManager {
|
|||
|
||||
UserManager(this._storage);
|
||||
|
||||
Future<List<int>> loadLocalUserIds() async {
|
||||
Future<List<int>?> loadLocalUserIds() async {
|
||||
return await _storage.readAll().then((userMap) {
|
||||
userMap.keys
|
||||
.where((id) => _isNumeric(id))
|
||||
|
|
|
@ -7,26 +7,26 @@ import 'package:vikunja_app/models/user.dart';
|
|||
@JsonSerializable()
|
||||
class Bucket {
|
||||
int id, listId, limit;
|
||||
String? title;
|
||||
double position;
|
||||
String title;
|
||||
DateTime created, updated;
|
||||
User createdBy;
|
||||
DateTime? created, updated;
|
||||
User? createdBy;
|
||||
bool isDoneBucket;
|
||||
List<Task> tasks;
|
||||
|
||||
Bucket({
|
||||
@required this.id,
|
||||
@required this.listId,
|
||||
required this.id,
|
||||
required this.listId,
|
||||
this.title,
|
||||
this.position,
|
||||
this.limit,
|
||||
this.isDoneBucket,
|
||||
this.position = 0,
|
||||
required this.limit,
|
||||
this.isDoneBucket = false,
|
||||
this.created,
|
||||
this.updated,
|
||||
this.createdBy,
|
||||
this.tasks,
|
||||
this.tasks = const <Task>[],
|
||||
});
|
||||
|
||||
List<Task> tasks = [];
|
||||
|
||||
Bucket.fromJSON(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
listId = json['list_id'],
|
||||
|
@ -42,9 +42,9 @@ class Bucket {
|
|||
? null
|
||||
: User.fromJson(json['created_by']),
|
||||
tasks = (json['tasks'] as List<dynamic>)
|
||||
?.map((task) => Task.fromJson(task))
|
||||
?.cast<Task>()
|
||||
?.toList() ?? <Task>[];
|
||||
.map((task) => Task.fromJson(task))
|
||||
.cast<Task>()
|
||||
.toList() ?? <Task>[];
|
||||
|
||||
toJSON() => {
|
||||
'id': id,
|
||||
|
@ -53,9 +53,9 @@ class Bucket {
|
|||
'position': position,
|
||||
'limit': limit,
|
||||
'is_done_bucket': isDoneBucket ?? false,
|
||||
'created': created?.toUtc()?.toIso8601String(),
|
||||
'updated': updated?.toUtc()?.toIso8601String(),
|
||||
'created': created?.toUtc().toIso8601String(),
|
||||
'updated': updated?.toUtc().toIso8601String(),
|
||||
'createdBy': createdBy?.toJSON(),
|
||||
'tasks': tasks?.map((task) => task.toJSON())?.toList(),
|
||||
'tasks': tasks?.map((task) => task.toJSON()).toList(),
|
||||
};
|
||||
}
|
|
@ -5,13 +5,14 @@ import 'package:vikunja_app/theme/constants.dart';
|
|||
|
||||
class Label {
|
||||
final int id;
|
||||
final String title, description;
|
||||
final DateTime created, updated;
|
||||
final User createdBy;
|
||||
final String? title, description;
|
||||
final DateTime? created, updated;
|
||||
final User? createdBy;
|
||||
final Color color;
|
||||
|
||||
Label(
|
||||
{this.id,
|
||||
{
|
||||
required this.id,
|
||||
this.title,
|
||||
this.description,
|
||||
this.color = vLabelDefaultColor,
|
||||
|
@ -24,7 +25,7 @@ class Label {
|
|||
title = json['title'],
|
||||
description = json['description'],
|
||||
color = json['hex_color'] == ''
|
||||
? null
|
||||
? vLabelDefaultColor
|
||||
: new Color(int.parse(json['hex_color'], radix: 16) + 0xFF000000),
|
||||
updated = DateTime.parse(json['updated']),
|
||||
created = DateTime.parse(json['created']),
|
||||
|
|
|
@ -4,9 +4,9 @@ import 'package:vikunja_app/models/task.dart';
|
|||
|
||||
class LabelTask {
|
||||
final Label label;
|
||||
final Task task;
|
||||
final Task? task;
|
||||
|
||||
LabelTask({@required this.label, @required this.task});
|
||||
LabelTask({required this.label, required this.task});
|
||||
|
||||
LabelTask.fromJson(Map<String, dynamic> json)
|
||||
: label = new Label(id: json['label_id']),
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:vikunja_app/models/label.dart';
|
|||
class LabelTaskBulk {
|
||||
final List<Label> labels;
|
||||
|
||||
LabelTaskBulk({@required this.labels});
|
||||
LabelTaskBulk({required this.labels});
|
||||
|
||||
LabelTaskBulk.fromJson(Map<String, dynamic> json)
|
||||
: labels = json['labels']?.map((label) => Label.fromJson(label));
|
||||
|
|
|
@ -4,33 +4,33 @@ import 'package:vikunja_app/models/user.dart';
|
|||
|
||||
class TaskList {
|
||||
final int id;
|
||||
final String title, description;
|
||||
final User owner;
|
||||
final DateTime created, updated;
|
||||
List<Task> tasks;
|
||||
final String? title, description;
|
||||
final User? owner;
|
||||
final DateTime? created, updated;
|
||||
List<Task?> tasks;
|
||||
final bool isFavorite;
|
||||
|
||||
TaskList(
|
||||
{@required this.id,
|
||||
@required this.title,
|
||||
{required this.id,
|
||||
required this.title,
|
||||
this.description,
|
||||
this.owner,
|
||||
this.created,
|
||||
this.updated,
|
||||
this.tasks,
|
||||
this.isFavorite});
|
||||
this.tasks = const <Task>[],
|
||||
this.isFavorite = false});
|
||||
|
||||
TaskList.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
owner = json['owner'] == null ? null : User.fromJson(json['owner']),
|
||||
owner = json['owner'] == null ? null : User.fromJson(json['owner']),
|
||||
description = json['description'],
|
||||
title = json['title'],
|
||||
updated = DateTime.parse(json['updated']),
|
||||
created = DateTime.parse(json['created']),
|
||||
isFavorite = json['is_favorite'],
|
||||
tasks = (json['tasks'] == null ? [] : json['tasks'] as List<dynamic>)
|
||||
?.map((taskJson) => Task.fromJson(taskJson))
|
||||
?.toList();
|
||||
.map((taskJson) => Task.fromJson(taskJson))
|
||||
.toList();
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
|
|
|
@ -3,15 +3,15 @@ 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,
|
||||
{required this.id,
|
||||
this.created,
|
||||
this.updated,
|
||||
@required this.title,
|
||||
required this.title,
|
||||
this.description,
|
||||
this.owner});
|
||||
|
||||
|
|
|
@ -5,16 +5,18 @@ 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 String? title, description, identifier;
|
||||
final bool? done;
|
||||
final Color? color;
|
||||
final List<DateTime?>? reminderDates;
|
||||
final String identifier;
|
||||
final String? title, description;
|
||||
final bool done;
|
||||
final Color color;
|
||||
final double? kanbanPosition;
|
||||
final User? createdBy;
|
||||
final Duration? repeatAfter;
|
||||
|
@ -23,16 +25,14 @@ class Task {
|
|||
final List<TaskAttachment>? attachments;
|
||||
// TODO: add position(?)
|
||||
|
||||
// // TODO: use `late final` once upgraded to current dart version
|
||||
late final CheckboxStatistics _checkboxStatistics;
|
||||
|
||||
bool loading = false;
|
||||
|
||||
// // TODO: use `late final` once upgraded to current dart version
|
||||
Task({
|
||||
@required this.id,
|
||||
required this.id,
|
||||
required this.identifier,
|
||||
this.title,
|
||||
this.description,
|
||||
this.identifier,
|
||||
this.done = false,
|
||||
this.reminderDates,
|
||||
this.dueDate,
|
||||
|
@ -41,7 +41,7 @@ class Task {
|
|||
this.parentTaskId,
|
||||
this.priority,
|
||||
this.repeatAfter,
|
||||
this.color,
|
||||
this.color = vBlue, // TODO: decide on color
|
||||
this.kanbanPosition,
|
||||
this.subtasks,
|
||||
this.labels,
|
||||
|
@ -53,16 +53,18 @@ class Task {
|
|||
this.bucketId,
|
||||
});
|
||||
|
||||
bool loading = false;
|
||||
|
||||
Task.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
title = json['title'],
|
||||
description = json['description'],
|
||||
identifier = json['identifier'],
|
||||
done = json['done'],
|
||||
reminderDates = (json['reminder_dates'] as List<dynamic>)
|
||||
?.map((ts) => DateTime.parse(ts))
|
||||
?.cast<DateTime>()
|
||||
?.toList(),
|
||||
reminderDates = json['reminder_dates'] != null ? (json['reminder_dates'] as List<dynamic>)
|
||||
.map((ts) => DateTime.parse(ts))
|
||||
.cast<DateTime>()
|
||||
.toList() : null,
|
||||
dueDate = DateTime.parse(json['due_date']),
|
||||
startDate = DateTime.parse(json['start_date']),
|
||||
endDate = DateTime.parse(json['end_date']),
|
||||
|
@ -70,23 +72,23 @@ class Task {
|
|||
priority = json['priority'],
|
||||
repeatAfter = Duration(seconds: json['repeat_after']),
|
||||
color = json['hex_color'] == ''
|
||||
? null
|
||||
? vBlue
|
||||
: new Color(int.parse(json['hex_color'], radix: 16) + 0xFF000000),
|
||||
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'] ?? []) 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(),
|
||||
updated = DateTime.parse(json['updated']),
|
||||
created = DateTime.parse(json['created']),
|
||||
listId = json['list_id'],
|
||||
|
@ -102,13 +104,13 @@ class Task {
|
|||
'identifier': identifier,
|
||||
'done': done ?? false,
|
||||
'reminder_dates':
|
||||
reminderDates?.map((date) => date.toUtc().toIso8601String()).toList(),
|
||||
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(),
|
||||
|
@ -119,17 +121,15 @@ class Task {
|
|||
'created': created?.toUtc().toIso8601String(),
|
||||
};
|
||||
|
||||
Color? get textColor => color != null
|
||||
? color!.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
||||
: null;
|
||||
Color? get textColor => color.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||
|
||||
CheckboxStatistics get checkboxStatistics {
|
||||
CheckboxStatistics? get checkboxStatistics {
|
||||
if (_checkboxStatistics != null)
|
||||
return _checkboxStatistics;
|
||||
if (description.isEmpty)
|
||||
if (description!.isEmpty)
|
||||
return null;
|
||||
|
||||
_checkboxStatistics = getCheckboxStatistics(description);
|
||||
_checkboxStatistics = getCheckboxStatistics(description!);
|
||||
return _checkboxStatistics;
|
||||
}
|
||||
|
||||
|
@ -141,22 +141,22 @@ class Task {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool get hasDueDate => dueDate.year != 1;
|
||||
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,
|
||||
bool done,
|
||||
Color color,
|
||||
bool resetColor,
|
||||
double kanbanPosition,
|
||||
User createdBy,
|
||||
Duration repeatAfter,
|
||||
List<Task> subtasks,
|
||||
List<Label> labels,
|
||||
List<TaskAttachment> attachments,
|
||||
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,
|
||||
double? kanbanPosition,
|
||||
User? createdBy,
|
||||
Duration? repeatAfter,
|
||||
List<Task>? subtasks,
|
||||
List<Label>? labels,
|
||||
List<TaskAttachment>? attachments,
|
||||
}) {
|
||||
return Task(
|
||||
id: id ?? this.id,
|
||||
|
@ -174,7 +174,7 @@ class Task {
|
|||
description: description ?? this.description,
|
||||
identifier: identifier ?? this.identifier,
|
||||
done: done ?? this.done,
|
||||
color: (resetColor ?? false) ? null : (color ?? this.color),
|
||||
color: (resetColor ?? false) ? vBlue : (color ?? this.color),
|
||||
kanbanPosition: kanbanPosition ?? this.kanbanPosition,
|
||||
createdBy: createdBy ?? this.createdBy,
|
||||
repeatAfter: repeatAfter ?? this.repeatAfter,
|
||||
|
|
|
@ -6,13 +6,13 @@ import 'package:vikunja_app/models/user.dart';
|
|||
@JsonSerializable()
|
||||
class TaskAttachment {
|
||||
int id, taskId;
|
||||
DateTime created;
|
||||
User createdBy;
|
||||
DateTime? created;
|
||||
User? createdBy;
|
||||
// TODO: add file
|
||||
|
||||
TaskAttachment({
|
||||
@required this.id,
|
||||
@required this.taskId,
|
||||
required this.id,
|
||||
required this.taskId,
|
||||
this.created,
|
||||
this.createdBy,
|
||||
});
|
||||
|
|
|
@ -13,8 +13,8 @@ class User {
|
|||
|
||||
toJSON() => {"id": this.id, "email": this.email, "username": this.username};
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,14 +22,14 @@ class HomePage extends StatefulWidget {
|
|||
class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
||||
List<Namespace> _namespaces = [];
|
||||
|
||||
Namespace get _currentNamespace =>
|
||||
Namespace? get _currentNamespace =>
|
||||
_selectedDrawerIndex >= 0 && _selectedDrawerIndex < _namespaces.length
|
||||
? _namespaces[_selectedDrawerIndex]
|
||||
: null;
|
||||
int _selectedDrawerIndex = -1, _previousDrawerIndex = -1;
|
||||
bool _loading = true;
|
||||
bool _showUserDetails = false;
|
||||
Widget drawerItem;
|
||||
Widget? drawerItem;
|
||||
|
||||
@override
|
||||
void afterFirstLayout(BuildContext context) {
|
||||
|
@ -42,7 +42,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),
|
||||
)));
|
||||
|
@ -102,7 +102,7 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NamespaceEditPage(
|
||||
namespace: _currentNamespace,
|
||||
namespace: _currentNamespace!,
|
||||
))).whenComplete(() => _loadNamespaces()))
|
||||
],
|
||||
),
|
||||
|
@ -110,9 +110,9 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
child: new Column(children: <Widget>[
|
||||
new UserAccountsDrawerHeader(
|
||||
accountEmail:
|
||||
currentUser?.email == null ? null : Text(currentUser.email),
|
||||
currentUser?.email == null ? null : Text(currentUser?.email ?? ""),
|
||||
accountName:
|
||||
currentUser?.username == null ? null : Text(currentUser.username),
|
||||
currentUser?.username == null ? null : Text(currentUser?.username ?? ""),
|
||||
onDetailsPressed: () {
|
||||
setState(() {
|
||||
_showUserDetails = !_showUserDetails;
|
||||
|
@ -189,7 +189,7 @@ class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
|
|||
_addNamespace(String name, BuildContext context) {
|
||||
VikunjaGlobal.of(context)
|
||||
.namespaceService
|
||||
.create(Namespace(id: null, title: name))
|
||||
.create(Namespace(id: 0, title: name))
|
||||
.then((_) {
|
||||
_loadNamespaces();
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
|
|
|
@ -12,7 +12,7 @@ import '../models/task.dart';
|
|||
class LandingPage extends StatefulWidget {
|
||||
|
||||
const LandingPage(
|
||||
{Key key})
|
||||
{Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -21,8 +21,8 @@ class LandingPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class LandingPageState extends State<LandingPage> with AfterLayoutMixin<LandingPage> {
|
||||
int defaultList;
|
||||
List<Task> _list;
|
||||
int? defaultList;
|
||||
List<Task>? _list;
|
||||
static const platform = const MethodChannel('vikunja');
|
||||
|
||||
|
||||
|
@ -69,7 +69,7 @@ class LandingPageState extends State<LandingPage> with AfterLayoutMixin<LandingP
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if(_list == null || _list.isEmpty)
|
||||
if(_list == null || _list!.isEmpty)
|
||||
_loadList(context);
|
||||
return new Scaffold(
|
||||
body: RefreshIndicator(
|
||||
|
@ -111,7 +111,7 @@ class LandingPageState extends State<LandingPage> with AfterLayoutMixin<LandingP
|
|||
|
||||
_addTask(Task task, BuildContext context) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
globalState.taskService.add(defaultList, task).then((_) {
|
||||
globalState.taskService.add(defaultList!, task).then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The task was added successfully!'),
|
||||
));
|
||||
|
@ -121,7 +121,7 @@ class LandingPageState extends State<LandingPage> with AfterLayoutMixin<LandingP
|
|||
|
||||
|
||||
List<Widget> _listTasks(BuildContext context) {
|
||||
var tasks = (_list.map((task) => _buildTile(task, context)) ?? []).toList();
|
||||
var tasks = (_list?.map((task) => _buildTile(task, context)) ?? []).toList();
|
||||
//tasks.addAll(_loadingTasks.map(_buildLoadingTile));
|
||||
return tasks;
|
||||
}
|
||||
|
|
|
@ -30,14 +30,14 @@ class BucketProps {
|
|||
bool scrollable = false;
|
||||
bool portrait = true;
|
||||
int bucketLength = 0;
|
||||
Size taskDropSize;
|
||||
Size? taskDropSize;
|
||||
}
|
||||
|
||||
class ListPage extends StatefulWidget {
|
||||
final TaskList taskList;
|
||||
|
||||
//ListPage({this.taskList}) : super(key: Key(taskList.id.toString()));
|
||||
ListPage({this.taskList}) : super(key: Key(Random().nextInt(100000).toString()));
|
||||
ListPage({required this.taskList}) : super(key: Key(Random().nextInt(100000).toString()));
|
||||
|
||||
@override
|
||||
_ListPageState createState() => _ListPageState();
|
||||
|
@ -46,15 +46,15 @@ class ListPage extends StatefulWidget {
|
|||
class _ListPageState extends State<ListPage> {
|
||||
final _keyboardController = KeyboardVisibilityController();
|
||||
int _viewIndex = 0;
|
||||
TaskList _list;
|
||||
TaskList? _list;
|
||||
List<Task> _loadingTasks = [];
|
||||
int _currentPage = 1;
|
||||
bool _loading = true;
|
||||
bool displayDoneTasks;
|
||||
ListProvider taskState;
|
||||
PageController _pageController;
|
||||
bool? displayDoneTasks;
|
||||
ListProvider? taskState;
|
||||
PageController? _pageController;
|
||||
Map<int, BucketProps> _bucketProps = {};
|
||||
int _draggedBucketIndex;
|
||||
int? _draggedBucketIndex;
|
||||
Duration _lastTaskDragUpdateAction = Duration.zero;
|
||||
|
||||
@override
|
||||
|
@ -80,7 +80,7 @@ class _ListPageState extends State<ListPage> {
|
|||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_list.title),
|
||||
title: Text(_list?.title ?? ""),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
|
@ -88,16 +88,16 @@ class _ListPageState extends State<ListPage> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ListEditPage(
|
||||
list: _list,
|
||||
list: _list!,
|
||||
),
|
||||
)).whenComplete(() => _loadList()),
|
||||
),
|
||||
],
|
||||
),
|
||||
// TODO: it brakes the flow with _loadingTasks and conflicts with the provider
|
||||
body: !taskState.isLoading
|
||||
body: !taskState!.isLoading
|
||||
? RefreshIndicator(
|
||||
child: taskState.tasks.length > 0 || taskState.buckets.length > 0
|
||||
child: taskState!.tasks.length > 0 || taskState!.buckets.length > 0
|
||||
? ListenableProvider.value(
|
||||
value: taskState,
|
||||
child: Theme(
|
||||
|
@ -163,6 +163,7 @@ class _ListPageState extends State<ListPage> {
|
|||
ListView _listView(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
itemCount: taskState!.tasks.length,
|
||||
itemBuilder: (context, i) {
|
||||
if (i.isOdd) return Divider();
|
||||
|
||||
|
@ -173,19 +174,22 @@ 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
|
||||
if (taskState.maxPages == _currentPage &&
|
||||
index == taskState.tasks.length)
|
||||
return null;
|
||||
|
||||
if (index >= taskState.tasks.length &&
|
||||
_currentPage < taskState.maxPages) {
|
||||
// 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");
|
||||
|
||||
if (index >= taskState!.tasks.length &&
|
||||
_currentPage < taskState!.maxPages) {
|
||||
_currentPage++;
|
||||
_loadTasksForPage(_currentPage);
|
||||
}
|
||||
return index < taskState.tasks.length
|
||||
? _buildTile(taskState.tasks[index])
|
||||
: null;
|
||||
return _buildTile(taskState!.tasks[index]);
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -197,7 +201,7 @@ class _ListPageState extends State<ListPage> {
|
|||
final bucketWidth = deviceData.size.width * bucketFraction;
|
||||
|
||||
if (_pageController == null) _pageController = PageController(viewportFraction: bucketFraction);
|
||||
else if (_pageController.viewportFraction != bucketFraction)
|
||||
else if (_pageController!.viewportFraction != bucketFraction)
|
||||
_pageController = PageController(viewportFraction: bucketFraction);
|
||||
|
||||
return ReorderableListView.builder(
|
||||
|
@ -205,17 +209,17 @@ class _ListPageState extends State<ListPage> {
|
|||
scrollController: _pageController,
|
||||
physics: PageScrollPhysics(),
|
||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
itemCount: taskState.buckets.length,
|
||||
itemCount: taskState?.buckets.length ?? 0,
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (context, index) {
|
||||
if (index > taskState.buckets.length) return null;
|
||||
if (index > (taskState!.buckets.length)) throw Exception("Check itemCount attribute");
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ValueKey<int>(index),
|
||||
index: index,
|
||||
enabled: taskState.buckets.length > 1 && !taskState.taskDragging,
|
||||
enabled: taskState!.buckets.length > 1 && !taskState!.taskDragging,
|
||||
child: SizedBox(
|
||||
width: bucketWidth,
|
||||
child: _buildBucketTile(taskState.buckets[index], portrait),
|
||||
child: _buildBucketTile(taskState!.buckets[index], portrait),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -257,37 +261,37 @@ class _ListPageState extends State<ListPage> {
|
|||
onReorder: (_, __) {},
|
||||
onReorderEnd: (newIndex) async {
|
||||
bool indexUpdated = false;
|
||||
if (newIndex > _draggedBucketIndex) {
|
||||
if (newIndex > _draggedBucketIndex!) {
|
||||
newIndex -= 1;
|
||||
indexUpdated = true;
|
||||
}
|
||||
|
||||
final movedBucket = taskState.buckets.removeAt(_draggedBucketIndex);
|
||||
if (newIndex >= taskState.buckets.length) {
|
||||
taskState.buckets.add(movedBucket);
|
||||
final movedBucket = taskState!.buckets.removeAt(_draggedBucketIndex!);
|
||||
if (newIndex >= taskState!.buckets.length) {
|
||||
taskState!.buckets.add(movedBucket);
|
||||
} else {
|
||||
taskState.buckets.insert(newIndex, movedBucket);
|
||||
taskState!.buckets.insert(newIndex, movedBucket);
|
||||
}
|
||||
|
||||
taskState.buckets[newIndex].position = calculateItemPosition(
|
||||
taskState!.buckets[newIndex].position = calculateItemPosition(
|
||||
positionBefore: newIndex != 0
|
||||
? taskState.buckets[newIndex - 1].position : null,
|
||||
positionAfter: newIndex < taskState.buckets.length - 1
|
||||
? taskState.buckets[newIndex + 1].position : null,
|
||||
? taskState!.buckets[newIndex - 1].position : null,
|
||||
positionAfter: newIndex < taskState!.buckets.length - 1
|
||||
? taskState!.buckets[newIndex + 1].position : null,
|
||||
);
|
||||
await _updateBucket(context, taskState.buckets[newIndex]);
|
||||
await _updateBucket(context, taskState!.buckets[newIndex]);
|
||||
|
||||
// make sure the first 2 buckets don't have 0 position
|
||||
if (newIndex == 0 && taskState.buckets.length > 1 && taskState.buckets[1].position == 0) {
|
||||
taskState.buckets[1].position = calculateItemPosition(
|
||||
positionBefore: taskState.buckets[0].position,
|
||||
positionAfter: 1 < taskState.buckets.length - 1
|
||||
? taskState.buckets[2].position : null,
|
||||
if (newIndex == 0 && taskState!.buckets.length > 1 && taskState!.buckets[1].position == 0) {
|
||||
taskState!.buckets[1].position = calculateItemPosition(
|
||||
positionBefore: taskState!.buckets[0].position,
|
||||
positionAfter: 1 < taskState!.buckets.length - 1
|
||||
? taskState!.buckets[2].position : null,
|
||||
);
|
||||
_updateBucket(context, taskState.buckets[1]);
|
||||
_updateBucket(context, taskState!.buckets[1]);
|
||||
}
|
||||
|
||||
if (indexUpdated && portrait) _pageController.animateToPage(
|
||||
if (indexUpdated && portrait) _pageController!.animateToPage(
|
||||
newIndex - 1,
|
||||
duration: Duration(milliseconds: 100),
|
||||
curve: Curves.easeInOut,
|
||||
|
@ -331,22 +335,22 @@ class _ListPageState extends State<ListPage> {
|
|||
|
||||
if (_bucketProps[bucket.id] == null)
|
||||
_bucketProps[bucket.id] = BucketProps();
|
||||
if (_bucketProps[bucket.id].bucketLength != (bucket.tasks.length)
|
||||
|| _bucketProps[bucket.id].portrait != portrait)
|
||||
if (_bucketProps[bucket.id]!.bucketLength != (bucket.tasks.length)
|
||||
|| _bucketProps[bucket.id]!.portrait != portrait)
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (_bucketProps[bucket.id].controller.hasClients) setState(() {
|
||||
_bucketProps[bucket.id].bucketLength = bucket.tasks.length;
|
||||
_bucketProps[bucket.id].scrollable = _bucketProps[bucket.id].controller.position.maxScrollExtent > 0;
|
||||
_bucketProps[bucket.id].portrait = portrait;
|
||||
if (_bucketProps[bucket.id]!.controller.hasClients) setState(() {
|
||||
_bucketProps[bucket.id]!.bucketLength = bucket.tasks.length;
|
||||
_bucketProps[bucket.id]!.scrollable = _bucketProps[bucket.id]!.controller.position.maxScrollExtent > 0;
|
||||
_bucketProps[bucket.id]!.portrait = portrait;
|
||||
});
|
||||
});
|
||||
if (_bucketProps[bucket.id].titleController.text.isEmpty)
|
||||
_bucketProps[bucket.id].titleController.text = bucket.title;
|
||||
if (_bucketProps[bucket.id]!.titleController.text.isEmpty)
|
||||
_bucketProps[bucket.id]!.titleController.text = bucket.title ?? "";
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
CustomScrollView(
|
||||
controller: _bucketProps[bucket.id].controller,
|
||||
controller: _bucketProps[bucket.id]!.controller,
|
||||
slivers: <Widget>[
|
||||
SliverBucketPersistentHeader(
|
||||
minExtent: bucketTitleHeight,
|
||||
|
@ -365,7 +369,7 @@ class _ListPageState extends State<ListPage> {
|
|||
children: <Widget>[
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _bucketProps[bucket.id].titleController,
|
||||
controller: _bucketProps[bucket.id]!.titleController,
|
||||
decoration: const InputDecoration.collapsed(
|
||||
hintText: 'Bucket Title',
|
||||
),
|
||||
|
@ -389,7 +393,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?.copyWith(
|
||||
color: bucket.limit != 0 && bucket.tasks.length >= bucket.limit
|
||||
? Colors.red : null,
|
||||
),
|
||||
|
@ -426,7 +430,7 @@ class _ListPageState extends State<ListPage> {
|
|||
}
|
||||
},
|
||||
itemBuilder: (context) {
|
||||
final enableDelete = taskState.buckets.length > 1;
|
||||
final bool enableDelete = (taskState?.buckets.length ?? 0) > 1;
|
||||
return <PopupMenuEntry<BucketMenu>>[
|
||||
PopupMenuItem<BucketMenu>(
|
||||
value: BucketMenu.limit,
|
||||
|
@ -479,22 +483,22 @@ class _ListPageState extends State<ListPage> {
|
|||
child: SliverBucketList(
|
||||
bucket: bucket,
|
||||
onTaskDragUpdate: (details) { // scroll when dragging a task
|
||||
if (details.sourceTimeStamp - _lastTaskDragUpdateAction > const Duration(milliseconds: 600)) {
|
||||
if (details.sourceTimeStamp! - _lastTaskDragUpdateAction > const Duration(milliseconds: 600)) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
const scrollDuration = Duration(milliseconds: 250);
|
||||
const scrollCurve = Curves.easeInOut;
|
||||
final updateAction = () => setState(() => _lastTaskDragUpdateAction = details.sourceTimeStamp);
|
||||
final updateAction = () => setState(() => _lastTaskDragUpdateAction = details.sourceTimeStamp!);
|
||||
if (details.globalPosition.dx < screenSize.width * 0.1) { // scroll left
|
||||
if (_pageController.position.extentBefore != 0)
|
||||
_pageController.previousPage(duration: scrollDuration, curve: scrollCurve);
|
||||
if (_pageController!.position.extentBefore != 0)
|
||||
_pageController!.previousPage(duration: scrollDuration, curve: scrollCurve);
|
||||
updateAction();
|
||||
} else if (details.globalPosition.dx > screenSize.width * 0.9) { // scroll right
|
||||
if (_pageController.position.extentAfter != 0)
|
||||
_pageController.nextPage(duration: scrollDuration, curve: scrollCurve);
|
||||
if (_pageController!.position.extentAfter != 0)
|
||||
_pageController!.nextPage(duration: scrollDuration, curve: scrollCurve);
|
||||
updateAction();
|
||||
} else {
|
||||
final viewingBucket = taskState.buckets[_pageController.page.floor()];
|
||||
final bucketController = _bucketProps[viewingBucket.id].controller;
|
||||
final viewingBucket = taskState!.buckets[_pageController!.page!.floor()];
|
||||
final bucketController = _bucketProps[viewingBucket.id]!.controller;
|
||||
if (details.globalPosition.dy < screenSize.height * 0.2) { // scroll up
|
||||
if (bucketController.position.extentBefore != 0)
|
||||
bucketController.animateTo(bucketController.offset - 80,
|
||||
|
@ -513,7 +517,7 @@ class _ListPageState extends State<ListPage> {
|
|||
),
|
||||
),
|
||||
SliverVisibility(
|
||||
visible: !_bucketProps[bucket.id].scrollable,
|
||||
visible: !_bucketProps[bucket.id]!.scrollable,
|
||||
maintainState: true,
|
||||
maintainAnimation: true,
|
||||
maintainSize: true,
|
||||
|
@ -523,9 +527,9 @@ class _ListPageState extends State<ListPage> {
|
|||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
if (_bucketProps[bucket.id].taskDropSize != null) DottedBorder(
|
||||
if (_bucketProps[bucket.id]!.taskDropSize != null) DottedBorder(
|
||||
color: Colors.grey,
|
||||
child: SizedBox.fromSize(size: _bucketProps[bucket.id].taskDropSize),
|
||||
child: SizedBox.fromSize(size: _bucketProps[bucket.id]!.taskDropSize),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
|
@ -536,7 +540,7 @@ class _ListPageState extends State<ListPage> {
|
|||
// DragTarget to drop tasks in empty buckets
|
||||
if (bucket.tasks.length == 0) DragTarget<TaskData>(
|
||||
onWillAccept: (data) {
|
||||
setState(() => _bucketProps[bucket.id].taskDropSize = data.size);
|
||||
setState(() => _bucketProps[bucket.id]!.taskDropSize = data?.size);
|
||||
return true;
|
||||
},
|
||||
onAccept: (data) {
|
||||
|
@ -548,9 +552,9 @@ class _ListPageState extends State<ListPage> {
|
|||
).then((_) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('${data.task.title} was moved to ${bucket.title} successfully!'),
|
||||
)));
|
||||
setState(() => _bucketProps[bucket.id].taskDropSize = null);
|
||||
setState(() => _bucketProps[bucket.id]!.taskDropSize = null);
|
||||
},
|
||||
onLeave: (_) => setState(() => _bucketProps[bucket.id].taskDropSize = null),
|
||||
onLeave: (_) => setState(() => _bucketProps[bucket.id]!.taskDropSize = null),
|
||||
builder: (_, __, ___) => SizedBox.expand(),
|
||||
),
|
||||
],
|
||||
|
@ -559,7 +563,7 @@ class _ListPageState extends State<ListPage> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (_bucketProps[bucket.id].scrollable) Align(
|
||||
if (_bucketProps[bucket.id]!.scrollable) Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: addTaskButton,
|
||||
),
|
||||
|
@ -568,7 +572,7 @@ class _ListPageState extends State<ListPage> {
|
|||
}
|
||||
|
||||
Future<void> updateDisplayDoneTasks() {
|
||||
return VikunjaGlobal.of(context).listService.getDisplayDoneTasks(_list.id)
|
||||
return VikunjaGlobal.of(context).listService.getDisplayDoneTasks(_list!.id)
|
||||
.then((value) {displayDoneTasks = value == "1";});
|
||||
}
|
||||
|
||||
|
@ -581,7 +585,7 @@ class _ListPageState extends State<ListPage> {
|
|||
MaterialPageRoute(
|
||||
builder: (context) => TaskEditPage(
|
||||
task: task,
|
||||
taskState: taskState,
|
||||
taskState: taskState!,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -597,7 +601,7 @@ class _ListPageState extends State<ListPage> {
|
|||
case 1:
|
||||
await _loadBucketsForPage(1);
|
||||
// load all buckets to get length for RecordableListView
|
||||
while (_currentPage < taskState.maxPages) {
|
||||
while (_currentPage < taskState!.maxPages) {
|
||||
_currentPage++;
|
||||
await _loadBucketsForPage(_currentPage);
|
||||
}
|
||||
|
@ -611,7 +615,7 @@ class _ListPageState extends State<ListPage> {
|
|||
Future<void> _loadTasksForPage(int page) {
|
||||
return Provider.of<ListProvider>(context, listen: false).loadTasks(
|
||||
context: context,
|
||||
listId: _list.id,
|
||||
listId: _list!.id,
|
||||
page: page,
|
||||
displayDoneTasks: displayDoneTasks ?? false
|
||||
);
|
||||
|
@ -620,12 +624,12 @@ class _ListPageState extends State<ListPage> {
|
|||
Future<void> _loadBucketsForPage(int page) {
|
||||
return Provider.of<ListProvider>(context, listen: false).loadBuckets(
|
||||
context: context,
|
||||
listId: _list.id,
|
||||
listId: _list!.id,
|
||||
page: page
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addItemDialog(BuildContext context, [Bucket bucket]) {
|
||||
Future<void> _addItemDialog(BuildContext context, [Bucket? bucket]) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (_) => AddDialog(
|
||||
|
@ -638,7 +642,7 @@ class _ListPageState extends State<ListPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _addItem(String title, BuildContext context, [Bucket bucket]) {
|
||||
Future<void> _addItem(String title, BuildContext context, [Bucket? bucket]) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
var newTask = Task(
|
||||
id: null,
|
||||
|
@ -646,13 +650,14 @@ class _ListPageState extends State<ListPage> {
|
|||
createdBy: globalState.currentUser,
|
||||
done: false,
|
||||
bucketId: bucket?.id,
|
||||
identifier: '',
|
||||
);
|
||||
setState(() => _loadingTasks.add(newTask));
|
||||
return Provider.of<ListProvider>(context, listen: false)
|
||||
.addTask(
|
||||
context: context,
|
||||
newTask: newTask,
|
||||
listId: _list.id,
|
||||
listId: _list!.id,
|
||||
)
|
||||
.then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
|
@ -682,12 +687,13 @@ class _ListPageState extends State<ListPage> {
|
|||
return Provider.of<ListProvider>(context, listen: false).addBucket(
|
||||
context: context,
|
||||
newBucket: Bucket(
|
||||
id: null,
|
||||
id: 0,
|
||||
title: title,
|
||||
createdBy: VikunjaGlobal.of(context).currentUser,
|
||||
listId: _list.id,
|
||||
listId: _list!.id,
|
||||
limit: 0,
|
||||
),
|
||||
listId: _list.id,
|
||||
listId: _list!.id,
|
||||
).then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('The bucket was added successfully!'),
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:vikunja_app/theme/buttonText.dart';
|
|||
class ListEditPage extends StatefulWidget {
|
||||
final TaskList list;
|
||||
|
||||
ListEditPage({this.list}) : super(key: Key(list.toString()));
|
||||
ListEditPage({required this.list}) : super(key: Key(list.toString()));
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ListEditPageState();
|
||||
|
@ -18,9 +18,9 @@ class ListEditPage extends StatefulWidget {
|
|||
class _ListEditPageState extends State<ListEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _loading = false;
|
||||
String _title, _description;
|
||||
bool displayDoneTasks;
|
||||
int listId;
|
||||
String? _title, _description;
|
||||
bool? displayDoneTasks;
|
||||
int listId = -1;
|
||||
|
||||
@override
|
||||
void initState(){
|
||||
|
@ -34,7 +34,7 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
VikunjaGlobal.of(context).listService.getDisplayDoneTasks(listId).then(
|
||||
(value) => setState(() => displayDoneTasks = value == "1"));
|
||||
else
|
||||
log("Display done tasks: " + displayDoneTasks?.toString());
|
||||
log("Display done tasks: " + displayDoneTasks.toString());
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Edit List'),
|
||||
|
@ -54,9 +54,9 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
initialValue: widget.list.title,
|
||||
onSaved: (title) => _title = title,
|
||||
validator: (title) {
|
||||
if (title.length < 3 || title.length > 250) {
|
||||
return 'The title needs to have between 3 and 250 characters.';
|
||||
}
|
||||
//if (title?.length < 3 || title.length > 250) {
|
||||
// return 'The title needs to have between 3 and 250 characters.';
|
||||
//}
|
||||
return null;
|
||||
},
|
||||
decoration: new InputDecoration(
|
||||
|
@ -73,6 +73,8 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
initialValue: widget.list.description,
|
||||
onSaved: (description) => _description = description,
|
||||
validator: (description) {
|
||||
if(description == null)
|
||||
return null;
|
||||
if (description.length > 1000) {
|
||||
return 'The description can have a maximum of 1000 characters.';
|
||||
}
|
||||
|
@ -115,12 +117,12 @@ class _ListEditPageState extends State<ListEditPage> {
|
|||
child: FancyButton(
|
||||
onPressed: !_loading
|
||||
? () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
Form.of(context).save();
|
||||
if (_formKey.currentState!.validate()) {
|
||||
Form.of(context)?.save();
|
||||
_saveList(context);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
: () {},
|
||||
child: _loading
|
||||
? CircularProgressIndicator()
|
||||
: VikunjaButtonText('Save'),
|
||||
|
|
|
@ -16,11 +16,9 @@ class TaskEditPage extends StatefulWidget {
|
|||
final ListProvider taskState;
|
||||
|
||||
TaskEditPage({
|
||||
@required this.task,
|
||||
@required this.taskState,
|
||||
}) : assert(task != null),
|
||||
assert(taskState != null),
|
||||
super(key: Key(task.toString()));
|
||||
required this.task,
|
||||
required this.taskState,
|
||||
}) : super(key: Key(task.toString()));
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _TaskEditPageState();
|
||||
|
@ -32,18 +30,18 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
bool _loading = false;
|
||||
bool _changed = false;
|
||||
|
||||
int _priority;
|
||||
DateTime _dueDate, _startDate, _endDate;
|
||||
List<DateTime> _reminderDates;
|
||||
String _title, _description, _repeatAfterType;
|
||||
Duration _repeatAfter;
|
||||
List<Label> _labels;
|
||||
int? _priority;
|
||||
DateTime? _dueDate, _startDate, _endDate;
|
||||
List<DateTime?>? _reminderDates;
|
||||
String? _title, _description, _repeatAfterType;
|
||||
Duration? _repeatAfter;
|
||||
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;
|
||||
List<Label>? _suggestedLabels;
|
||||
var _reminderInputs = <Widget>[];
|
||||
final _labelTypeAheadController = TextEditingController();
|
||||
Color _color;
|
||||
Color _pickerColor;
|
||||
Color? _color;
|
||||
Color? _pickerColor;
|
||||
bool _resetColor = false;
|
||||
|
||||
@override
|
||||
|
@ -52,11 +50,11 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
if (_reminderDates == null) {
|
||||
_reminderDates = widget.task.reminderDates ?? [];
|
||||
|
||||
_reminderDates?.asMap()?.forEach((i, time) =>
|
||||
setState(() => _reminderInputs?.add(VikunjaDateTimePicker(
|
||||
_reminderDates!.asMap().forEach((i, time) =>
|
||||
setState(() => _reminderInputs.add(VikunjaDateTimePicker(
|
||||
initialValue: time,
|
||||
label: 'Reminder',
|
||||
onSaved: (reminder) => _reminderDates[i] = reminder,
|
||||
onSaved: (reminder) { if(reminder == null) return null; _reminderDates![i] = reminder; },
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
@ -68,7 +66,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
return WillPopScope(
|
||||
onWillPop: () {
|
||||
if(_changed) {
|
||||
return _showConfirmationDialog();
|
||||
return (_showConfirmationDialog() ?? Future.value(false));
|
||||
}
|
||||
return new Future(() => true);
|
||||
},
|
||||
|
@ -95,9 +93,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
onSaved: (title) => _title = title,
|
||||
onChanged: (_) => _changed = true,
|
||||
validator: (title) {
|
||||
if (title.length < 3 || title.length > 250) {
|
||||
return 'The title needs to have between 3 and 250 characters.';
|
||||
}
|
||||
//if (title.length < 3 || title.length > 250) {
|
||||
// return 'The title needs to have between 3 and 250 characters.';
|
||||
//}
|
||||
return null;
|
||||
},
|
||||
decoration: new InputDecoration(
|
||||
|
@ -115,6 +113,8 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
onSaved: (description) => _description = description,
|
||||
onChanged: (_) => _changed = true,
|
||||
validator: (description) {
|
||||
if (description == null)
|
||||
return null;
|
||||
if (description.length > 1000) {
|
||||
return 'The description can have a maximum of 1000 characters.';
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
value: _repeatAfterType ??
|
||||
getRepeatAfterTypeFromDuration(
|
||||
widget.task.repeatAfter),
|
||||
onChanged: (String newValue) {
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_repeatAfterType = newValue;
|
||||
});
|
||||
|
@ -217,15 +217,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(null);
|
||||
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,
|
||||
onChanged: (_) => _changed = true,
|
||||
initialValue: DateTime.now(),
|
||||
),
|
||||
|
@ -242,7 +242,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
value: _priorityToString(_priority),
|
||||
isExpanded: true,
|
||||
isDense: true,
|
||||
onChanged: (String newValue) {
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_priority = _priorityFromString(newValue);
|
||||
});
|
||||
|
@ -258,7 +258,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
children: _labels.map((Label label) {
|
||||
children: _labels!.map((Label label) {
|
||||
return LabelComponent(
|
||||
label: label,
|
||||
onDelete: () {
|
||||
|
@ -277,14 +277,13 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
InputDecoration(labelText: 'Add a new label')),
|
||||
suggestionsCallback: (pattern) => _searchLabel(pattern),
|
||||
itemBuilder: (context, suggestion) {
|
||||
print(suggestion);
|
||||
return new ListTile(title: Text(suggestion));
|
||||
return new ListTile(title: Text(suggestion.toString()));
|
||||
},
|
||||
transitionBuilder: (context, suggestionsBox, controller) {
|
||||
return suggestionsBox;
|
||||
},
|
||||
onSuggestionSelected: (suggestion) {
|
||||
_addLabel(suggestion);
|
||||
_addLabel(suggestion.toString());
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -309,7 +308,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
ElevatedButton(
|
||||
child: Text(
|
||||
'Color',
|
||||
style: _resetColor || (_color ?? widget.task.color) == null ? null : TextStyle(
|
||||
style: _resetColor ? null : TextStyle(
|
||||
color: (_color ?? widget.task.color)
|
||||
.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
),
|
||||
|
@ -323,8 +322,8 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
child: () {
|
||||
String colorString = (_resetColor ? null : (_color ?? widget.task.color))?.toString();
|
||||
colorString = colorString?.substring(10, colorString.length - 1)?.toUpperCase();
|
||||
String? colorString = (_resetColor ? null : (_color ?? widget.task.color))?.toString();
|
||||
colorString = colorString?.substring(10, colorString.length - 1).toUpperCase();
|
||||
colorString = colorString != null ? '#$colorString' : 'None';
|
||||
return Text(
|
||||
'$colorString',
|
||||
|
@ -345,9 +344,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: !_loading ? () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
Form.of(_listKey.currentContext).save();
|
||||
_saveTask(_listKey.currentContext);
|
||||
if (_formKey.currentState!.validate()) {
|
||||
Form.of(_listKey.currentContext!)!.save();
|
||||
_saveTask(_listKey.currentContext!);
|
||||
}
|
||||
} : null,
|
||||
child: Icon(Icons.save),
|
||||
|
@ -361,7 +360,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 == null);
|
||||
|
||||
Task updatedTask = widget.task.copyWith(
|
||||
title: _title,
|
||||
|
@ -414,7 +413,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
|
||||
_removeLabel(Label label) {
|
||||
setState(() {
|
||||
_labels.removeWhere((l) => l.id == label.id);
|
||||
_labels?.removeWhere((l) => l.id == label.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -422,9 +421,9 @@ 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();
|
||||
List<String?> labelText = labels.map((label) => label.title).toList();
|
||||
return labelText;
|
||||
});
|
||||
}
|
||||
|
@ -432,9 +431,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
_addLabel(String labelTitle) {
|
||||
// FIXME: This is not an optimal solution...
|
||||
bool found = false;
|
||||
_suggestedLabels.forEach((label) {
|
||||
_suggestedLabels?.forEach((label) {
|
||||
if (label.title == labelTitle) {
|
||||
_labels.add(label);
|
||||
_labels?.add(label);
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
|
@ -451,20 +450,20 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
return;
|
||||
}
|
||||
|
||||
Label newLabel = Label(title: labelTitle);
|
||||
Label newLabel = Label(title: labelTitle, id: 0);
|
||||
VikunjaGlobal.of(context)
|
||||
.labelService
|
||||
.create(newLabel)
|
||||
.then((createdLabel) {
|
||||
setState(() {
|
||||
_labels.add(createdLabel);
|
||||
_labels?.add(createdLabel);
|
||||
_labelTypeAheadController.clear();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: Move the following two functions to an extra class or type.
|
||||
_priorityFromString(String priority) {
|
||||
_priorityFromString(String? priority) {
|
||||
switch (priority) {
|
||||
case 'Low':
|
||||
return 1;
|
||||
|
@ -482,7 +481,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
}
|
||||
}
|
||||
|
||||
_priorityToString(int priority) {
|
||||
_priorityToString(int? priority) {
|
||||
switch (priority) {
|
||||
case 0:
|
||||
return 'Unset';
|
||||
|
@ -502,7 +501,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
}
|
||||
|
||||
_onColorEdit() {
|
||||
_pickerColor = _resetColor || (_color ?? widget.task.color) == null
|
||||
_pickerColor = _resetColor
|
||||
? Colors.black
|
||||
: _color ?? widget.task.color;
|
||||
showDialog(
|
||||
|
@ -511,7 +510,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
title: const Text('Task Color'),
|
||||
content: SingleChildScrollView(
|
||||
child: ColorPicker(
|
||||
pickerColor: _pickerColor,
|
||||
pickerColor: _pickerColor!,
|
||||
enableAlpha: false,
|
||||
labelTypes: const [ColorLabelType.hsl, ColorLabelType.rgb],
|
||||
paletteType: PaletteType.hslWithLightness,
|
||||
|
@ -557,7 +556,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
}
|
||||
|
||||
Future<bool> _showConfirmationDialog() async {
|
||||
return showDialog<bool>(
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
|
@ -588,6 +587,6 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
) ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import 'package:vikunja_app/stores/list_store.dart';
|
|||
class NamespacePage extends StatefulWidget {
|
||||
final Namespace namespace;
|
||||
|
||||
NamespacePage({this.namespace}) : super(key: Key(namespace.id.toString()));
|
||||
NamespacePage({required this.namespace}) : super(key: Key(namespace.id.toString()));
|
||||
|
||||
@override
|
||||
_NamespacePageState createState() => new _NamespacePageState();
|
||||
|
@ -47,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),
|
||||
),
|
||||
|
@ -126,7 +126,7 @@ class _NamespacePageState extends State<NamespacePage>
|
|||
_addList(String name, BuildContext context) {
|
||||
VikunjaGlobal.of(context)
|
||||
.listService
|
||||
.create(widget.namespace.id, TaskList(id: null, title: name, tasks: []))
|
||||
.create(widget.namespace.id, TaskList(id: 0, title: name, tasks: []))
|
||||
.then((_) {
|
||||
setState(() {});
|
||||
_loadLists();
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'package:vikunja_app/theme/buttonText.dart';
|
|||
class NamespaceEditPage extends StatefulWidget {
|
||||
final Namespace namespace;
|
||||
|
||||
NamespaceEditPage({this.namespace}) : super(key: Key(namespace.toString()));
|
||||
NamespaceEditPage({required this.namespace}) : super(key: Key(namespace.toString()));
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NamespaceEditPageState();
|
||||
|
@ -16,7 +16,7 @@ class NamespaceEditPage extends StatefulWidget {
|
|||
class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _loading = false;
|
||||
String _name, _description;
|
||||
String? _name, _description;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext ctx) {
|
||||
|
@ -39,9 +39,9 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
initialValue: widget.namespace.title,
|
||||
onSaved: (name) => _name = name,
|
||||
validator: (name) {
|
||||
if (name.length < 3 || name.length > 250) {
|
||||
return 'The name needs to have between 3 and 250 characters.';
|
||||
}
|
||||
//if (name.length < 3 || name.length > 250) {
|
||||
// return 'The name needs to have between 3 and 250 characters.';
|
||||
//}
|
||||
return null;
|
||||
},
|
||||
decoration: new InputDecoration(
|
||||
|
@ -58,9 +58,9 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
initialValue: widget.namespace.description,
|
||||
onSaved: (description) => _description = description,
|
||||
validator: (description) {
|
||||
if (description.length > 1000) {
|
||||
return 'The description can have a maximum of 1000 characters.';
|
||||
}
|
||||
//if (description.length > 1000) {
|
||||
// return 'The description can have a maximum of 1000 characters.';
|
||||
//}
|
||||
return null;
|
||||
},
|
||||
decoration: new InputDecoration(
|
||||
|
@ -75,12 +75,12 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
|||
child: FancyButton(
|
||||
onPressed: !_loading
|
||||
? () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
Form.of(context).save();
|
||||
if (_formKey.currentState!.validate()) {
|
||||
Form.of(context)?.save();
|
||||
_saveNamespace(context);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
: () => null,
|
||||
child: _loading
|
||||
? CircularProgressIndicator()
|
||||
: VikunjaButtonText('Save'),
|
||||
|
|
|
@ -10,9 +10,9 @@ class SettingsPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class SettingsPageState extends State<SettingsPage> {
|
||||
List<TaskList> taskListList;
|
||||
int defaultList;
|
||||
bool ignoreCertificates;
|
||||
List<TaskList>? taskListList;
|
||||
int? defaultList;
|
||||
bool? ignoreCertificates;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -31,10 +31,10 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
taskListList != null ?
|
||||
ListTile(
|
||||
title: Text("Default List"),
|
||||
trailing: DropdownButton(
|
||||
items: [DropdownMenuItem(child: Text("None"), value: null,), ...taskListList.map((e) => DropdownMenuItem(child: Text(e.title), value: e.id)).toList()],
|
||||
trailing: DropdownButton<int>(
|
||||
items: [DropdownMenuItem(child: Text("None"), value: null,), ...taskListList!.map((e) => DropdownMenuItem(child: Text(e.title ?? ""), value: e.id)).toList()],
|
||||
value: defaultList,
|
||||
onChanged: (value){
|
||||
onChanged: (int? value){
|
||||
setState(() => defaultList = value);
|
||||
VikunjaGlobal.of(context).listService.setDefaultList(value);
|
||||
},
|
||||
|
|
|
@ -41,8 +41,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||
content: Text(
|
||||
"Login has expired. Please reenter your details!")));
|
||||
setState(() {
|
||||
_serverController.text = VikunjaGlobal.of(context)?.client?.base;
|
||||
_usernameController.text = VikunjaGlobal.of(context)?.currentUser?.username;
|
||||
_serverController.text = VikunjaGlobal.of(context).client.base ?? "";
|
||||
_usernameController.text = VikunjaGlobal.of(context).currentUser?.username ?? "";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -86,7 +86,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
controller: _serverController,
|
||||
autocorrect: false,
|
||||
validator: (address) {
|
||||
return isUrl(address) || address.isEmpty ? null : 'Invalid URL';
|
||||
return (isUrl(address) || address != null || address!.isEmpty) ? null : 'Invalid URL';
|
||||
},
|
||||
decoration: new InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
|
@ -118,7 +118,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
padding: vStandardVerticalPadding,
|
||||
child: CheckboxListTile(
|
||||
value: _rememberMe,
|
||||
onChanged: (value) => setState( () =>_rememberMe = value),
|
||||
onChanged: (value) => setState( () => _rememberMe = value ?? false),
|
||||
title: Text("Remember me"),
|
||||
),
|
||||
),
|
||||
|
@ -126,12 +126,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||
builder: (context) => FancyButton(
|
||||
onPressed: !_loading
|
||||
? () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
Form.of(context).save();
|
||||
if (_formKey.currentState!.validate()) {
|
||||
Form.of(context)?.save();
|
||||
_loginUser(context);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
: () => null,
|
||||
child: _loading
|
||||
? CircularProgressIndicator()
|
||||
: VikunjaButtonText('Login'),
|
||||
|
@ -146,7 +146,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
)),
|
||||
Builder(builder: (context) => FancyButton(
|
||||
onPressed: () {
|
||||
if(_formKey.currentState.validate() && _serverController.text.isNotEmpty) {
|
||||
if(_formKey.currentState!.validate() && _serverController.text.isNotEmpty) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) =>
|
||||
|
@ -158,9 +158,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||
child: VikunjaButtonText("Login with Frontend"))),
|
||||
client.ignoreCertificates != null ?
|
||||
CheckboxListTile(title: Text("Ignore Certificates"), value: client.ignoreCertificates, onChanged: (value) {
|
||||
setState(() => client.reload_ignore_certs(value));
|
||||
VikunjaGlobal.of(context).settingsManager.setIgnoreCertificates(value);
|
||||
VikunjaGlobal.of(context).client.ignoreCertificates = value;
|
||||
setState(() => client.reload_ignore_certs(value ?? false));
|
||||
VikunjaGlobal.of(context).settingsManager.setIgnoreCertificates(value ?? false);
|
||||
VikunjaGlobal.of(context).client.ignoreCertificates = value ?? false;
|
||||
}) : ListTile(title: Text("..."))
|
||||
],
|
||||
),
|
||||
|
@ -187,14 +187,14 @@ class _LoginPageState extends State<LoginPage> {
|
|||
Server info = await vGlobal.serverService.getInfo();
|
||||
|
||||
|
||||
UserTokenPair newUser;
|
||||
UserTokenPair? newUser;
|
||||
|
||||
try {
|
||||
newUser =
|
||||
await vGlobal.newUserService.login(
|
||||
await vGlobal.newUserService?.login(
|
||||
_username, _password, rememberMe: this._rememberMe);
|
||||
} catch (e) {
|
||||
if (e.runtimeType == InvalidRequestApiException && e.errorCode == 412) {
|
||||
if (e is ApiException && e.errorCode == 412) {
|
||||
TextEditingController totpController = TextEditingController();
|
||||
await showDialog(context: context, builder: (context) =>
|
||||
new AlertDialog(
|
||||
|
@ -208,14 +208,15 @@ class _LoginPageState extends State<LoginPage> {
|
|||
],
|
||||
));
|
||||
newUser =
|
||||
await vGlobal.newUserService.login(
|
||||
await vGlobal.newUserService?.login(
|
||||
_username, _password, rememberMe: this._rememberMe,
|
||||
totp: totpController.text);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
vGlobal.changeUser(newUser.user, token: newUser.token, base: _server);
|
||||
if(newUser != null)
|
||||
vGlobal.changeUser(newUser.user, token: newUser.token, base: _server);
|
||||
} catch (ex, stacktrace) {
|
||||
log(stacktrace.toString());
|
||||
throw ex;
|
||||
|
@ -244,8 +245,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||
vGS.client.configure(token: baseTokenPair.token, base: baseTokenPair.base, authenticated: true);
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
var newUser = await vGS.newUserService.getCurrentUser();
|
||||
vGS.changeUser(newUser, token: baseTokenPair.token, base: baseTokenPair.base);
|
||||
var newUser = await vGS.newUserService?.getCurrentUser();
|
||||
if(newUser != null)
|
||||
vGS.changeUser(newUser, token: baseTokenPair.token, base: baseTokenPair.base);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ class LoginWithWebView extends StatefulWidget {
|
|||
|
||||
class LoginWithWebViewState extends State<LoginWithWebView> {
|
||||
|
||||
WebView webView;
|
||||
WebViewController webViewController;
|
||||
WebView? webView;
|
||||
WebViewController? webViewController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -30,7 +30,7 @@ class LoginWithWebViewState extends State<LoginWithWebView> {
|
|||
userAgent: "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Mobile Safari/537.36",
|
||||
onWebViewCreated: (controller) {
|
||||
webViewController = controller;
|
||||
webViewController.runJavascript("localStorage.clear(); location.href=location.href;");
|
||||
webViewController!.runJavascript("localStorage.clear(); location.href=location.href;");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -42,19 +42,22 @@ class LoginWithWebViewState extends State<LoginWithWebView> {
|
|||
body: webView
|
||||
),
|
||||
onWillPop: () async {
|
||||
String currentUrl = await webViewController.currentUrl();
|
||||
bool hasPopped = await _handlePageFinished(currentUrl);
|
||||
return Future.value(!hasPopped);
|
||||
String? currentUrl = await webViewController?.currentUrl();
|
||||
if (currentUrl != null) {
|
||||
bool hasPopped = await _handlePageFinished(currentUrl);
|
||||
return Future.value(!hasPopped);
|
||||
}
|
||||
return Future.value(false);
|
||||
},);
|
||||
}
|
||||
|
||||
Future<bool> _handlePageFinished(String pageLocation) async {
|
||||
log("handlePageFinished");
|
||||
if(webViewController != null) {
|
||||
String localStorage = await webViewController
|
||||
String localStorage = await webViewController!
|
||||
.runJavascriptReturningResult("JSON.stringify(localStorage);");
|
||||
|
||||
String apiUrl = await webViewController.runJavascriptReturningResult("API_URL");
|
||||
String apiUrl = await webViewController!.runJavascriptReturningResult("API_URL");
|
||||
if (localStorage != "{}") {
|
||||
apiUrl = apiUrl.replaceAll("\"", "");
|
||||
if(!apiUrl.startsWith("http")) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class RegisterPage extends StatefulWidget {
|
|||
class _RegisterPageState extends State<RegisterPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final passwordController = TextEditingController();
|
||||
String _server, _username, _email, _password;
|
||||
String? _server, _username, _email, _password;
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
|
@ -46,9 +46,9 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||
Padding(
|
||||
padding: vStandardVerticalPadding,
|
||||
child: TextFormField(
|
||||
onSaved: (username) => _username = username.trim(),
|
||||
onSaved: (username) => _username = username?.trim(),
|
||||
validator: (username) {
|
||||
return username.trim().isNotEmpty
|
||||
return username!.trim().isNotEmpty
|
||||
? null
|
||||
: 'Please specify a username';
|
||||
},
|
||||
|
@ -77,7 +77,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||
controller: passwordController,
|
||||
onSaved: (password) => _password = password,
|
||||
validator: (password) {
|
||||
return password.length >= 8
|
||||
return (password?.length ?? 0) >= 8
|
||||
? null
|
||||
: 'Please use at least 8 characters';
|
||||
},
|
||||
|
@ -105,14 +105,14 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||
builder: (context) => FancyButton(
|
||||
onPressed: !_loading
|
||||
? () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
Form.of(context).save();
|
||||
if (_formKey.currentState!.validate()) {
|
||||
Form.of(context)?.save();
|
||||
_registerUser(context);
|
||||
} else {
|
||||
print("awhat");
|
||||
}
|
||||
}
|
||||
: null,
|
||||
: () => null,
|
||||
child: _loading
|
||||
? CircularProgressIndicator()
|
||||
: VikunjaButtonText('Register'),
|
||||
|
@ -129,9 +129,10 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||
var vGlobal = VikunjaGlobal.of(context);
|
||||
var newUserLoggedIn = await vGlobal
|
||||
.newUserService
|
||||
.register(_username, _email, _password);
|
||||
vGlobal.changeUser(newUserLoggedIn.user,
|
||||
token: newUserLoggedIn.token, base: _server);
|
||||
?.register(_username!, _email, _password);
|
||||
if(newUserLoggedIn != null)
|
||||
vGlobal.changeUser(newUserLoggedIn.user,
|
||||
token: newUserLoggedIn.token, base: _server!);
|
||||
} catch (ex) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
|
@ -45,6 +45,7 @@ var _tasks = {
|
|||
created: DateTime.now(),
|
||||
description: 'A descriptive task',
|
||||
done: false,
|
||||
identifier: '',
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -84,7 +85,7 @@ class MockedNamespaceService implements NamespaceService {
|
|||
class MockedListService implements ListService {
|
||||
@override
|
||||
Future<TaskList> create(namespaceId, TaskList tl) {
|
||||
_nsLists[namespaceId].add(tl.id);
|
||||
_nsLists[namespaceId]?.add(tl.id);
|
||||
return Future.value(_lists[tl.id] = tl);
|
||||
}
|
||||
|
||||
|
@ -107,7 +108,7 @@ class MockedListService implements ListService {
|
|||
@override
|
||||
Future<List<TaskList>> getByNamespace(int namespaceId) {
|
||||
return Future.value(
|
||||
_nsLists[namespaceId].map((listId) => _lists[listId]).toList());
|
||||
_nsLists[namespaceId]!.map((listId) => _lists[listId]!).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -135,7 +136,7 @@ class MockedListService implements ListService {
|
|||
}
|
||||
|
||||
@override
|
||||
void setDefaultList(int listId) {
|
||||
void setDefaultList(int? listId) {
|
||||
// TODO: implement setDefaultList
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +145,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();
|
||||
}
|
||||
|
@ -152,25 +153,25 @@ 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] = task);
|
||||
return Future.value(_tasks[task.id ?? 0] = task);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Task> add(int listId, Task task) {
|
||||
var id = _tasks.keys.last + 1;
|
||||
_tasks[id] = task;
|
||||
_lists[listId].tasks.add(task);
|
||||
_lists[listId]!.tasks.add(task);
|
||||
return Future.value(task);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> getAllByList(int listId,
|
||||
[Map<String, List<String>> queryParameters]) {
|
||||
[Map<String, List<String>>? queryParameters]) {
|
||||
return Future.value(new Response(_tasks.values.toList(), 200, {}));
|
||||
}
|
||||
|
||||
|
@ -196,13 +197,13 @@ class MockedTaskService implements TaskService {
|
|||
|
||||
class MockedUserService implements UserService {
|
||||
@override
|
||||
Future<UserTokenPair> login(String username, password, {bool rememberMe = false, String totp}) {
|
||||
return Future.value(UserTokenPair(_users[1], 'abcdefg'));
|
||||
Future<UserTokenPair> login(String username, password, {bool rememberMe = false, String? totp}) {
|
||||
return Future.value(UserTokenPair(_users[1]!, 'abcdefg'));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserTokenPair> register(String username, email, password) {
|
||||
return Future.value(UserTokenPair(_users[1], 'abcdefg'));
|
||||
return Future.value(UserTokenPair(_users[1]!, 'abcdefg'));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -43,7 +43,7 @@ class TaskServiceOption<T> {
|
|||
}
|
||||
|
||||
class TaskServiceOptions {
|
||||
List<TaskServiceOption> options;
|
||||
List<TaskServiceOption>? options;
|
||||
|
||||
TaskServiceOptions({this.options}) {
|
||||
if(this.options == null)
|
||||
|
@ -58,16 +58,18 @@ class TaskServiceOptions {
|
|||
}
|
||||
|
||||
void setOption(TaskServiceOption option, dynamic value) {
|
||||
options.firstWhere((element) => element.name == option.name).value = value;
|
||||
options?.firstWhere((element) => element.name == option.name).value = value;
|
||||
}
|
||||
|
||||
String getOptions() {
|
||||
String result = '';
|
||||
for(TaskServiceOption option in options) {
|
||||
if(options == null)
|
||||
return '';
|
||||
for(TaskServiceOption option in options!) {
|
||||
dynamic value = option.getValue();
|
||||
if (value is List) {
|
||||
for (dynamic value_entry in value) {
|
||||
result += '&' + option.name + '[]=' + value_entry;
|
||||
for (dynamic valueEntry in value) {
|
||||
result += '&' + option.name + '[]=' + valueEntry;
|
||||
}
|
||||
} else {
|
||||
result += '&' + option.name + '=' + value;
|
||||
|
@ -98,7 +100,7 @@ abstract class ListService {
|
|||
Future<String> getDisplayDoneTasks(int listId);
|
||||
void setDisplayDoneTasks(int listId, String value);
|
||||
Future<String?> getDefaultList();
|
||||
void setDefaultList(int listId);
|
||||
void setDefaultList(int? listId);
|
||||
}
|
||||
|
||||
abstract class TaskService {
|
||||
|
@ -168,7 +170,7 @@ class SettingsManager {
|
|||
_storage.write(key: key, value: value);
|
||||
});});}
|
||||
|
||||
Future<String> getIgnoreCertificates() {
|
||||
Future<String?> getIgnoreCertificates() {
|
||||
return _storage.read(key: "ignore-certificates");
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class ListProvider with ChangeNotifier {
|
|||
|
||||
List<Bucket> get buckets => _buckets;
|
||||
|
||||
Future<void> loadTasks({BuildContext context, int listId, int page = 1, bool displayDoneTasks = true}) {
|
||||
Future<void> loadTasks({required BuildContext context, required int listId, int page = 1, bool displayDoneTasks = true}) {
|
||||
_tasks = [];
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
@ -57,7 +57,7 @@ class ListProvider with ChangeNotifier {
|
|||
}
|
||||
return VikunjaGlobal.of(context).taskService.getAllByList(listId, queryParams).then((response) {
|
||||
if (response.headers["x-pagination-total-pages"] != null) {
|
||||
_maxPages = int.parse(response.headers["x-pagination-total-pages"]);
|
||||
_maxPages = int.parse(response.headers["x-pagination-total-pages"]!);
|
||||
}
|
||||
_tasks.addAll(response.body);
|
||||
|
||||
|
@ -66,7 +66,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> loadBuckets({BuildContext context, int listId, int page = 1}) {
|
||||
Future<void> loadBuckets({required BuildContext context, required int listId, int page = 1}) {
|
||||
_buckets = [];
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
@ -77,7 +77,7 @@ class ListProvider with ChangeNotifier {
|
|||
|
||||
return VikunjaGlobal.of(context).bucketService.getAllByList(listId, queryParams).then((response) {
|
||||
if (response.headers["x-pagination-total-pages"] != null) {
|
||||
_maxPages = int.parse(response.headers["x-pagination-total-pages"]);
|
||||
_maxPages = int.parse(response.headers["x-pagination-total-pages"]!);
|
||||
}
|
||||
_buckets.addAll(response.body);
|
||||
|
||||
|
@ -87,10 +87,11 @@ class ListProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> addTaskByTitle(
|
||||
{BuildContext context, String title, int listId}) {
|
||||
{required BuildContext context, required String title, required int listId}) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
var newTask = Task(
|
||||
id: null,
|
||||
id: 0,
|
||||
identifier: '',
|
||||
title: title,
|
||||
createdBy: globalState.currentUser,
|
||||
done: false,
|
||||
|
@ -105,7 +106,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> addTask({BuildContext context, Task newTask, int listId}) {
|
||||
Future<void> addTask({required BuildContext context, required Task newTask, required int listId}) {
|
||||
var globalState = VikunjaGlobal.of(context);
|
||||
if (newTask.bucketId == null) _isLoading = true;
|
||||
notifyListeners();
|
||||
|
@ -126,7 +127,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<Task> updateTask({BuildContext context, Task task}) {
|
||||
Future<Task> updateTask({required BuildContext context, required Task task}) {
|
||||
return VikunjaGlobal.of(context).taskService.update(task).then((task) {
|
||||
// FIXME: This is ugly. We should use a redux to not have to do these kind of things.
|
||||
// This is enough for now (it works™) but we should definitly fix it later.
|
||||
|
@ -145,7 +146,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> addBucket({BuildContext context, Bucket newBucket, int listId}) {
|
||||
Future<void> addBucket({required BuildContext context, required Bucket newBucket, required int listId}) {
|
||||
notifyListeners();
|
||||
return VikunjaGlobal.of(context).bucketService.add(listId, newBucket)
|
||||
.then((bucket) {
|
||||
|
@ -154,7 +155,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> updateBucket({BuildContext context, Bucket bucket}) {
|
||||
Future<void> updateBucket({required BuildContext context, required Bucket bucket}) {
|
||||
return VikunjaGlobal.of(context).bucketService.update(bucket)
|
||||
.then((rBucket) {
|
||||
_buckets[_buckets.indexWhere((b) => rBucket.id == b.id)] = rBucket;
|
||||
|
@ -163,7 +164,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> deleteBucket({BuildContext context, int listId, int bucketId}) {
|
||||
Future<void> deleteBucket({required BuildContext context, required int listId, required int bucketId}) {
|
||||
return VikunjaGlobal.of(context).bucketService.delete(listId, bucketId)
|
||||
.then((_) {
|
||||
_buckets.removeWhere((bucket) => bucket.id == bucketId);
|
||||
|
@ -171,7 +172,7 @@ class ListProvider with ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> moveTaskToBucket({BuildContext context, Task task, int newBucketId, int index}) async {
|
||||
Future<void> moveTaskToBucket({required BuildContext context, required Task task, required 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--;
|
||||
|
@ -194,7 +195,7 @@ class ListProvider with ChangeNotifier {
|
|||
_buckets[newBucketIndex].tasks[index] = task;
|
||||
|
||||
// make sure the first 2 tasks don't have 0 kanbanPosition
|
||||
Task secondTask;
|
||||
Task? secondTask;
|
||||
if (index == 0 && _buckets[newBucketIndex].tasks.length > 1
|
||||
&& _buckets[newBucketIndex].tasks[1].kanbanPosition == 0) {
|
||||
secondTask = await VikunjaGlobal.of(context).taskService.update(
|
||||
|
@ -210,11 +211,12 @@ 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;
|
||||
if (secondTask != null)
|
||||
_tasks[_tasks.indexWhere((t) => t.id == secondTask?.id)] = secondTask;
|
||||
}
|
||||
|
||||
_buckets[newBucketIndex].tasks[_buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task.id)] = task;
|
||||
_buckets[newBucketIndex].tasks.sort((a, b) => a.kanbanPosition.compareTo(b.kanbanPosition));
|
||||
_buckets[newBucketIndex].tasks.sort((a, b) => a.kanbanPosition!.compareTo(b.kanbanPosition!));
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@ import 'package:vikunja_app/theme/constants.dart';
|
|||
class FancyButton extends StatelessWidget {
|
||||
final double width;
|
||||
final double height;
|
||||
final Function onPressed;
|
||||
final VoidCallback? onPressed;
|
||||
final Widget child;
|
||||
|
||||
const FancyButton({
|
||||
Key key,
|
||||
@required this.child,
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.width = double.infinity,
|
||||
this.height = 35,
|
||||
this.onPressed,
|
||||
|
|
|
@ -6,7 +6,7 @@ class VikunjaButtonText extends StatelessWidget {
|
|||
|
||||
const VikunjaButtonText(
|
||||
this.text, {
|
||||
Key key,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
|
@ -26,7 +26,7 @@ ThemeData _buildVikunjaTheme(ThemeData base) {
|
|||
// title: base.textTheme.title.copyWith(
|
||||
// fontFamily: 'Quicksand',
|
||||
// ),
|
||||
button: base.textTheme.button.copyWith(
|
||||
button: base.textTheme.button?.copyWith(
|
||||
color:
|
||||
vWhite, // This does not work, looks like a bug in Flutter: https://github.com/flutter/flutter/issues/19623
|
||||
),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:math';
|
||||
|
||||
double calculateItemPosition({double positionBefore, double positionAfter}) {
|
||||
double calculateItemPosition({double? positionBefore, double? positionAfter}) {
|
||||
// only
|
||||
if (positionBefore == null && positionAfter == null) {
|
||||
return 0;
|
||||
|
@ -17,5 +17,5 @@ double calculateItemPosition({double positionBefore, double positionAfter}) {
|
|||
}
|
||||
|
||||
// in the middle (positionBefore != null && positionAfter != null)
|
||||
return (positionBefore + positionAfter) / 2;
|
||||
return (positionBefore! + positionAfter!) / 2;
|
||||
}
|
|
@ -5,8 +5,8 @@ class CheckboxStatistics {
|
|||
final int checked;
|
||||
|
||||
const CheckboxStatistics({
|
||||
@required this.total,
|
||||
@required this.checked,
|
||||
required this.total,
|
||||
required this.checked,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,8 @@ class MatchedCheckboxes {
|
|||
final Iterable<Match> unchecked;
|
||||
|
||||
const MatchedCheckboxes({
|
||||
@required this.checked,
|
||||
@required this.unchecked,
|
||||
required this.checked,
|
||||
required this.unchecked,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,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))
|
||||
checked.add(match);
|
||||
else
|
||||
unchecked.add(match);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
getRepeatAfterTypeFromDuration(Duration repeatAfter) {
|
||||
getRepeatAfterTypeFromDuration(Duration? repeatAfter) {
|
||||
if (repeatAfter == null || repeatAfter.inSeconds == 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ getRepeatAfterTypeFromDuration(Duration repeatAfter) {
|
|||
return 'Hours';
|
||||
}
|
||||
|
||||
getRepeatAfterValueFromDuration(Duration repeatAfter) {
|
||||
getRepeatAfterValueFromDuration(Duration? repeatAfter) {
|
||||
if (repeatAfter == null || repeatAfter.inSeconds == 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ getRepeatAfterValueFromDuration(Duration repeatAfter) {
|
|||
return repeatAfter.inHours;
|
||||
}
|
||||
|
||||
getDurationFromType(String value, String type) {
|
||||
getDurationFromType(String? value, String? type) {
|
||||
// Return an empty duration if either of the values is not set
|
||||
if (value == null || value == '' || type == null || type == '') {
|
||||
return Duration();
|
||||
|
|
|
@ -720,7 +720,7 @@ packages:
|
|||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0+1"
|
||||
version: "0.2.0+2"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -4,7 +4,7 @@ description: Vikunja as Flutter cross platform app
|
|||
version: 0.2.0+200099
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
|
Loading…
Reference in New Issue
Block a user