mirror of
https://github.com/go-vikunja/app
synced 2024-06-01 02:06:51 +00:00
refactor: upgrade all deps
This commit is contained in:
parent
e0c9f1a10e
commit
facadb5d8f
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -7,7 +7,7 @@
|
|||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"main"
|
||||
"core"
|
||||
],
|
||||
}
|
||||
]
|
||||
|
|
|
@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
@ -41,8 +41,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "io.vikunja.app"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
minSdkVersion 33
|
||||
targetSdkVersion 34
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
>
|
||||
<meta-data android:name="io.flutter.network-policy" android:resource="@xml/network_security_config"/>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config>
|
||||
<base-config cleartextTrafficPermitted="true">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.7.20'
|
||||
ext.kotlin_version = '1.9.23'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
|
|
@ -9,7 +9,6 @@ import 'package:vikunja_app/global.dart';
|
|||
|
||||
import '../main.dart';
|
||||
|
||||
|
||||
class Client {
|
||||
GlobalKey<ScaffoldMessengerState>? global_scaffold_key;
|
||||
final JsonDecoder _decoder = new JsonDecoder();
|
||||
|
@ -25,8 +24,6 @@ class Client {
|
|||
|
||||
String? post_body;
|
||||
|
||||
|
||||
|
||||
bool operator ==(dynamic otherClient) {
|
||||
return otherClient._token == _token;
|
||||
}
|
||||
|
@ -36,18 +33,17 @@ class Client {
|
|||
configure(token: token, base: base, authenticated: authenticated);
|
||||
}
|
||||
|
||||
void reload_ignore_certs(bool? val) {
|
||||
void reloadIgnoreCerts(bool? val) {
|
||||
ignoreCertificates = val ?? false;
|
||||
HttpOverrides.global = new IgnoreCertHttpOverrides(ignoreCertificates);
|
||||
if(global_scaffold_key == null || global_scaffold_key!.currentContext == null) return;
|
||||
VikunjaGlobal
|
||||
.of(global_scaffold_key!.currentContext!)
|
||||
if (global_scaffold_key == null ||
|
||||
global_scaffold_key!.currentContext == null) return;
|
||||
VikunjaGlobal.of(global_scaffold_key!.currentContext!)
|
||||
.settingsManager
|
||||
.setIgnoreCertificates(ignoreCertificates);
|
||||
}
|
||||
|
||||
get _headers =>
|
||||
{
|
||||
get _headers => {
|
||||
'Authorization': _token != '' ? 'Bearer $_token' : '',
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Vikunja Mobile App'
|
||||
|
@ -59,20 +55,15 @@ class Client {
|
|||
int get hashCode => _token.hashCode;
|
||||
|
||||
void configure({String? token, String? base, bool? authenticated}) {
|
||||
if (token != null)
|
||||
_token = token;
|
||||
if (token != null) _token = token;
|
||||
if (base != null) {
|
||||
base = base.replaceAll(" ", "");
|
||||
if(base.endsWith("/"))
|
||||
base = base.substring(0,base.length-1);
|
||||
if (base.endsWith("/")) base = base.substring(0, base.length - 1);
|
||||
_base = base.endsWith('/api/v1') ? base : '$base/api/v1';
|
||||
}
|
||||
if (authenticated != null)
|
||||
this.authenticated = authenticated;
|
||||
|
||||
if (authenticated != null) this.authenticated = authenticated;
|
||||
}
|
||||
|
||||
|
||||
void reset() {
|
||||
_token = _base = '';
|
||||
authenticated = false;
|
||||
|
@ -84,54 +75,61 @@ class Client {
|
|||
// why are we doing it like this? because Uri doesnt have setters. wtf.
|
||||
|
||||
uri = Uri(
|
||||
scheme: uri.scheme,
|
||||
scheme: uri.scheme,
|
||||
userInfo: uri.userInfo,
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
path: uri.path,
|
||||
//queryParameters: {...uri.queryParameters, ...?queryParameters},
|
||||
queryParameters: queryParameters,
|
||||
fragment: uri.fragment
|
||||
);
|
||||
fragment: uri.fragment);
|
||||
|
||||
return http.get(uri, headers: _headers)
|
||||
.then(_handleResponse).onError((error, stackTrace) =>
|
||||
_handleError(error, stackTrace));
|
||||
return http
|
||||
.get(uri, headers: _headers)
|
||||
.then(_handleResponse)
|
||||
.onError((error, stackTrace) => _handleError(error, stackTrace));
|
||||
}
|
||||
|
||||
Future<Response?> delete(String url) {
|
||||
return http.delete(
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
).then(_handleResponse).onError((error, stackTrace) =>
|
||||
_handleError(error, stackTrace));
|
||||
return http
|
||||
.delete(
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
)
|
||||
.then(_handleResponse)
|
||||
.onError((error, stackTrace) => _handleError(error, stackTrace));
|
||||
}
|
||||
|
||||
Future<Response?> post(String url, {dynamic body}) {
|
||||
return http.post(
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
body: _encoder.convert(body),
|
||||
)
|
||||
.then(_handleResponse).onError((error, stackTrace) =>
|
||||
_handleError(error, stackTrace));
|
||||
return http
|
||||
.post(
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
body: _encoder.convert(body),
|
||||
)
|
||||
.then(_handleResponse)
|
||||
.onError((error, stackTrace) => _handleError(error, stackTrace));
|
||||
}
|
||||
|
||||
Future<Response?> put(String url, {dynamic body}) {
|
||||
return http.put(
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
body: _encoder.convert(body),
|
||||
)
|
||||
.then(_handleResponse).onError((error, stackTrace) =>
|
||||
_handleError(error, stackTrace));
|
||||
return http
|
||||
.put(
|
||||
'${this.base}$url'.toUri()!,
|
||||
headers: _headers,
|
||||
body: _encoder.convert(body),
|
||||
)
|
||||
.then(_handleResponse)
|
||||
.onError((error, stackTrace) => _handleError(error, stackTrace));
|
||||
}
|
||||
|
||||
Response? _handleError(Object? e, StackTrace? st) {
|
||||
if(global_scaffold_key == null) return null;
|
||||
if (global_scaffold_key == null) return null;
|
||||
SnackBar snackBar = SnackBar(
|
||||
content: Text("Error on request: " + e.toString()),
|
||||
action: SnackBarAction(label: "Clear", onPressed: () => global_scaffold_key!.currentState?.clearSnackBars()),);
|
||||
action: SnackBarAction(
|
||||
label: "Clear",
|
||||
onPressed: () => global_scaffold_key!.currentState?.clearSnackBars()),
|
||||
);
|
||||
global_scaffold_key!.currentState?.showSnackBar(snackBar);
|
||||
return null;
|
||||
}
|
||||
|
@ -144,39 +142,38 @@ class Client {
|
|||
return map;
|
||||
}
|
||||
|
||||
|
||||
Error? _handleResponseErrors(http.Response response) {
|
||||
if (response.statusCode < 200 ||
|
||||
response.statusCode >= 400) {
|
||||
if (response.statusCode < 200 || response.statusCode >= 400) {
|
||||
Map<String, dynamic> error;
|
||||
error = _decoder.convert(response.body);
|
||||
|
||||
|
||||
final SnackBar snackBar = SnackBar(
|
||||
content: Text(
|
||||
"Error code " + response.statusCode.toString() + " received."),
|
||||
action: globalNavigatorKey.currentContext == null ? null : SnackBarAction(
|
||||
label: ("Details"),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: globalNavigatorKey.currentContext!,
|
||||
builder: (BuildContext context) =>
|
||||
AlertDialog(
|
||||
title: Text("Error ${response.statusCode}"),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Message: ${error["message"]}", textAlign: TextAlign.start,),
|
||||
Text("Url: ${response.request!.url.toString()}"),
|
||||
],
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
),
|
||||
content:
|
||||
Text("Error code " + response.statusCode.toString() + " received."),
|
||||
action: globalNavigatorKey.currentContext == null
|
||||
? null
|
||||
: SnackBarAction(
|
||||
label: ("Details"),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: globalNavigatorKey.currentContext!,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text("Error ${response.statusCode}"),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"Message: ${error["message"]}",
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
Text("Url: ${response.request!.url.toString()}"),
|
||||
],
|
||||
)));
|
||||
},
|
||||
),
|
||||
);
|
||||
if(global_scaffold_key != null && showSnackBar)
|
||||
if (global_scaffold_key != null && showSnackBar)
|
||||
global_scaffold_key!.currentState?.showSnackBar(snackBar);
|
||||
else
|
||||
print("error on request: ${error["message"]}");
|
||||
|
|
|
@ -8,7 +8,9 @@ import 'package:vikunja_app/service/services.dart';
|
|||
|
||||
class ListAPIService extends APIService implements ListService {
|
||||
FlutterSecureStorage _storage;
|
||||
ListAPIService(Client client, FlutterSecureStorage storage) : _storage = storage, super(client);
|
||||
ListAPIService(Client client, FlutterSecureStorage storage)
|
||||
: _storage = storage,
|
||||
super(client);
|
||||
|
||||
@override
|
||||
Future<TaskList?> create(namespaceId, TaskList tl) {
|
||||
|
@ -16,9 +18,9 @@ class ListAPIService extends APIService implements ListService {
|
|||
return client
|
||||
.put('/namespaces/$namespaceId/lists', body: tl.toJSON())
|
||||
.then((response) {
|
||||
if (response == null) return null;
|
||||
return TaskList.fromJson(response.body);
|
||||
});
|
||||
if (response == null) return null;
|
||||
return TaskList.fromJson(response.body);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -32,12 +34,10 @@ class ListAPIService extends APIService implements ListService {
|
|||
if (response == null) return null;
|
||||
final map = response.body;
|
||||
if (map.containsKey('id')) {
|
||||
return client
|
||||
.get("/lists/$listId/tasks")
|
||||
.then((tasks) {
|
||||
map['tasks'] = tasks?.body;
|
||||
return TaskList.fromJson(map);
|
||||
});
|
||||
return client.get("/lists/$listId/tasks").then((tasks) {
|
||||
map['tasks'] = tasks?.body;
|
||||
return TaskList.fromJson(map);
|
||||
});
|
||||
}
|
||||
return TaskList.fromJson(map);
|
||||
});
|
||||
|
@ -45,47 +45,44 @@ class ListAPIService extends APIService implements ListService {
|
|||
|
||||
@override
|
||||
Future<List<TaskList>?> getAll() {
|
||||
return client.get('/lists').then(
|
||||
(list) {
|
||||
if (list == null || list.statusCode != 200) return null;
|
||||
if (list.body.toString().isEmpty)
|
||||
return Future.value([]);
|
||||
print(list.statusCode);
|
||||
return convertList(list.body, (result) => TaskList.fromJson(result));});
|
||||
return client.get('/lists').then((list) {
|
||||
if (list == null || list.statusCode != 200) return null;
|
||||
if (list.body.toString().isEmpty) return Future.value([]);
|
||||
print(list.statusCode);
|
||||
return convertList(list.body, (result) => TaskList.fromJson(result));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TaskList>?> getByNamespace(int namespaceId) {
|
||||
// TODO there needs to be a better way for this. /namespaces/-2/lists should
|
||||
// return favorite lists
|
||||
if(namespaceId == -2) {
|
||||
if (namespaceId == -2) {
|
||||
// Favourites.
|
||||
return getAll().then((value) {
|
||||
if (value == null) return null;
|
||||
value.removeWhere((element) => !element.isFavorite); return value;});
|
||||
value.removeWhere((element) => !element.isFavorite);
|
||||
return value;
|
||||
});
|
||||
}
|
||||
return client.get('/namespaces/$namespaceId/lists').then(
|
||||
(list) {
|
||||
if (list == null || list.statusCode != 200) return null;
|
||||
return convertList(list.body, (result) => TaskList.fromJson(result));
|
||||
});
|
||||
return client.get('/namespaces/$namespaceId/lists').then((list) {
|
||||
if (list == null || list.statusCode != 200) return null;
|
||||
return convertList(list.body, (result) => TaskList.fromJson(result));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TaskList?> update(TaskList tl) {
|
||||
return client
|
||||
.post('/lists/${tl.id}', body: tl.toJSON())
|
||||
.then((response) {
|
||||
if (response == null) return null;
|
||||
return TaskList.fromJson(response.body);
|
||||
});
|
||||
return client.post('/lists/${tl.id}', body: tl.toJSON()).then((response) {
|
||||
if (response == null) return null;
|
||||
return TaskList.fromJson(response.body);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getDisplayDoneTasks(int listId) {
|
||||
return _storage.read(key: "display_done_tasks_list_$listId").then((value)
|
||||
{
|
||||
if(value == null) {
|
||||
return _storage.read(key: "display_done_tasks_list_$listId").then((value) {
|
||||
if (value == null) {
|
||||
// TODO: implement default value
|
||||
setDisplayDoneTasks(listId, "1");
|
||||
return Future.value("1");
|
||||
|
@ -104,7 +101,6 @@ class ListAPIService extends APIService implements ListService {
|
|||
return _storage.read(key: "default_list_id");
|
||||
}
|
||||
|
||||
@override
|
||||
void setDefaultList(int? listId) {
|
||||
_storage.write(key: "default_list_id", value: listId.toString());
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ import 'package:vikunja_app/service/services.dart';
|
|||
class ProjectAPIService extends APIService implements ProjectService {
|
||||
FlutterSecureStorage _storage;
|
||||
|
||||
ProjectAPIService(client, storage) : _storage = storage, super(client);
|
||||
ProjectAPIService(client, storage)
|
||||
: _storage = storage,
|
||||
super(client);
|
||||
|
||||
@override
|
||||
Future<Project?> create(Project p) {
|
||||
|
@ -47,9 +49,7 @@ class ProjectAPIService extends APIService implements ProjectService {
|
|||
|
||||
@override
|
||||
Future<Project?> update(Project p) {
|
||||
return client
|
||||
.post('/projects/${p.id}', body: p.toJSON())
|
||||
.then((response) {
|
||||
return client.post('/projects/${p.id}', body: p.toJSON()).then((response) {
|
||||
if (response == null) return null;
|
||||
return Project.fromJson(response.body);
|
||||
});
|
||||
|
@ -57,9 +57,8 @@ class ProjectAPIService extends APIService implements ProjectService {
|
|||
|
||||
@override
|
||||
Future<String> getDisplayDoneTasks(int listId) {
|
||||
return _storage.read(key: "display_done_tasks_list_$listId").then((value)
|
||||
{
|
||||
if(value == null) {
|
||||
return _storage.read(key: "display_done_tasks_list_$listId").then((value) {
|
||||
if (value == null) {
|
||||
// TODO: implement default value
|
||||
setDisplayDoneTasks(listId, "1");
|
||||
return Future.value("1");
|
||||
|
@ -68,19 +67,15 @@ class ProjectAPIService extends APIService implements ProjectService {
|
|||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void setDisplayDoneTasks(int listId, String value) {
|
||||
_storage.write(key: "display_done_tasks_list_$listId", value: value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getDefaultList() {
|
||||
return _storage.read(key: "default_list_id");
|
||||
}
|
||||
|
||||
@override
|
||||
void setDefaultList(int? listId) {
|
||||
_storage.write(key: "default_list_id", value: listId.toString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:vikunja_app/theme/constants.dart';
|
|||
|
||||
import '../stores/project_store.dart';
|
||||
|
||||
enum DropLocation {above, below, none}
|
||||
enum DropLocation { above, below, none }
|
||||
|
||||
class TaskData {
|
||||
final Task task;
|
||||
|
@ -37,7 +37,8 @@ class BucketTaskCard extends StatefulWidget {
|
|||
State<BucketTaskCard> createState() => _BucketTaskCardState();
|
||||
}
|
||||
|
||||
class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAliveClientMixin {
|
||||
class _BucketTaskCardState extends State<BucketTaskCard>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Size? _cardSize;
|
||||
bool _dragging = false;
|
||||
DropLocation _dropLocation = DropLocation.none;
|
||||
|
@ -49,7 +50,8 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
if (_cardSize == null) _updateCardSize(context);
|
||||
|
||||
final taskState = Provider.of<ProjectProvider>(context);
|
||||
final bucket = taskState.buckets[taskState.buckets.indexWhere((b) => b.id == widget.task.bucketId)];
|
||||
final bucket = taskState.buckets[
|
||||
taskState.buckets.indexWhere((b) => b.id == widget.task.bucketId)];
|
||||
// default chip height: 32
|
||||
const double chipHeight = 28;
|
||||
const chipConstraints = BoxConstraints(maxHeight: chipHeight);
|
||||
|
@ -59,7 +61,8 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
children: <Widget>[
|
||||
Text(
|
||||
widget.task.identifier.isNotEmpty
|
||||
? '#${widget.task.identifier}' : '${widget.task.id}',
|
||||
? '#${widget.task.identifier}'
|
||||
: '${widget.task.id}',
|
||||
style: (theme.textTheme.titleSmall ?? TextStyle()).copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
|
@ -67,21 +70,25 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
],
|
||||
);
|
||||
if (widget.task.done) {
|
||||
identifierRow.children.insert(0, Container(
|
||||
constraints: chipConstraints,
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: FittedBox(
|
||||
child: Chip(
|
||||
label: Text('Done'),
|
||||
labelStyle: (theme.textTheme.labelLarge ?? TextStyle()).copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.brightness == Brightness.dark
|
||||
? Colors.black : Colors.white,
|
||||
identifierRow.children.insert(
|
||||
0,
|
||||
Container(
|
||||
constraints: chipConstraints,
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: FittedBox(
|
||||
child: Chip(
|
||||
label: Text('Done'),
|
||||
labelStyle:
|
||||
(theme.textTheme.labelLarge ?? TextStyle()).copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.brightness == Brightness.dark
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
),
|
||||
backgroundColor: vGreen,
|
||||
),
|
||||
),
|
||||
backgroundColor: vGreen,
|
||||
),
|
||||
),
|
||||
));
|
||||
));
|
||||
}
|
||||
|
||||
final titleRow = Row(
|
||||
|
@ -89,9 +96,11 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
Expanded(
|
||||
child: Text(
|
||||
widget.task.title,
|
||||
style: (theme.textTheme.titleMedium ?? TextStyle(fontSize: 16)).copyWith(
|
||||
style: (theme.textTheme.titleMedium ?? TextStyle(fontSize: 16))
|
||||
.copyWith(
|
||||
color: theme.brightness == Brightness.dark
|
||||
? Colors.white : Colors.black,
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -145,10 +154,10 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
backgroundColor: Colors.grey,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
(checkboxStatistics.checked == checkboxStatistics.total ? '' : '${checkboxStatistics.checked} of ')
|
||||
+ '${checkboxStatistics.total} tasks'
|
||||
),
|
||||
label: Text((checkboxStatistics.checked == checkboxStatistics.total
|
||||
? ''
|
||||
: '${checkboxStatistics.checked} of ') +
|
||||
'${checkboxStatistics.total} tasks'),
|
||||
));
|
||||
}
|
||||
if (widget.task.attachments.isNotEmpty) {
|
||||
|
@ -185,7 +194,8 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
child: identifierRow,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 4, bottom: labelRow.children.isNotEmpty ? 8 : 0),
|
||||
padding: EdgeInsets.only(
|
||||
top: 4, bottom: labelRow.children.isNotEmpty ? 8 : 0),
|
||||
child: Container(
|
||||
constraints: rowConstraints,
|
||||
child: titleRow,
|
||||
|
@ -213,7 +223,9 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
|
||||
return LongPressDraggable<TaskData>(
|
||||
data: TaskData(widget.task, _cardSize),
|
||||
maxSimultaneousDrags: taskState.taskDragging ? 0 : 1, // only one task can be dragged at a time
|
||||
maxSimultaneousDrags: taskState.taskDragging
|
||||
? 0
|
||||
: 1, // only one task can be dragged at a time
|
||||
onDragStarted: () {
|
||||
taskState.taskDragging = true;
|
||||
setState(() => _dragging = true);
|
||||
|
@ -223,14 +235,16 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
taskState.taskDragging = false;
|
||||
setState(() => _dragging = false);
|
||||
},
|
||||
feedback: (_cardSize == null) ? SizedBox.shrink() : SizedBox.fromSize(
|
||||
size: _cardSize,
|
||||
child: Card(
|
||||
color: card.color,
|
||||
child: (card.child as InkWell).child,
|
||||
elevation: (card.elevation ?? 0) + 5,
|
||||
),
|
||||
),
|
||||
feedback: (_cardSize == null)
|
||||
? SizedBox.shrink()
|
||||
: SizedBox.fromSize(
|
||||
size: _cardSize,
|
||||
child: Card(
|
||||
color: card.color,
|
||||
child: (card.child as InkWell).child,
|
||||
elevation: (card.elevation ?? 0) + 5,
|
||||
),
|
||||
),
|
||||
childWhenDragging: SizedBox.shrink(),
|
||||
child: () {
|
||||
if (_dragging || _cardSize == null) return card;
|
||||
|
@ -241,25 +255,30 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
color: Colors.grey,
|
||||
child: SizedBox.fromSize(size: dropBoxSize),
|
||||
);
|
||||
final dropAbove = taskState.taskDragging && _dropLocation == DropLocation.above;
|
||||
final dropBelow = taskState.taskDragging && _dropLocation == DropLocation.below;
|
||||
final DragTargetLeave<TaskData> dragTargetOnLeave = (data) => setState(() {
|
||||
_dropLocation = DropLocation.none;
|
||||
_dropData = null;
|
||||
});
|
||||
final dragTargetOnWillAccept = (TaskData data, DropLocation dropLocation) {
|
||||
if (data.task.bucketId != bucket.id)
|
||||
if (bucket.limit != 0 && bucket.tasks.length >= bucket.limit)
|
||||
return false;
|
||||
final dropAbove =
|
||||
taskState.taskDragging && _dropLocation == DropLocation.above;
|
||||
final dropBelow =
|
||||
taskState.taskDragging && _dropLocation == DropLocation.below;
|
||||
final DragTargetLeave<TaskData> dragTargetOnLeave =
|
||||
(data) => setState(() {
|
||||
_dropLocation = DropLocation.none;
|
||||
_dropData = null;
|
||||
});
|
||||
final dragTargetOnWillAccept =
|
||||
(TaskData data, DropLocation dropLocation) {
|
||||
if (data.task.bucketId != bucket.id) if (bucket.limit != 0 &&
|
||||
bucket.tasks.length >= bucket.limit) return false;
|
||||
setState(() {
|
||||
_dropLocation = dropLocation;
|
||||
_dropData = data;
|
||||
});
|
||||
return true;
|
||||
};
|
||||
final DragTargetAccept<TaskData> dragTargetOnAccept = (data) {
|
||||
final DragTargetAccept<DragTargetDetails<TaskData>> dragTargetOnAccept =
|
||||
(data) {
|
||||
final index = bucket.tasks.indexOf(widget.task);
|
||||
widget.onAccept(data.task, _dropLocation == DropLocation.above ? index : index + 1);
|
||||
widget.onAccept(data.data.task,
|
||||
_dropLocation == DropLocation.above ? index : index + 1);
|
||||
setState(() {
|
||||
_dropLocation = DropLocation.none;
|
||||
_dropData = null;
|
||||
|
@ -268,7 +287,8 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
|
||||
return SizedBox(
|
||||
width: cardSize.width,
|
||||
height: cardSize.height + (dropAbove || dropBelow ? dropBoxSize.height + 4 : 0),
|
||||
height: cardSize.height +
|
||||
(dropAbove || dropBelow ? dropBoxSize.height + 4 : 0),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
|
@ -281,18 +301,22 @@ 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>(
|
||||
onWillAcceptWithDetails: (data) => dragTargetOnWillAccept(data, DropLocation.above),
|
||||
onWillAcceptWithDetails: (data) =>
|
||||
dragTargetOnWillAccept(data.data, DropLocation.above),
|
||||
onAcceptWithDetails: 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>(
|
||||
onWillAcceptWithDetails: (data) => dragTargetOnWillAccept(data, DropLocation.below),
|
||||
onWillAcceptWithDetails: (data) =>
|
||||
dragTargetOnWillAccept(data.data, DropLocation.below),
|
||||
onAcceptWithDetails: dragTargetOnAccept,
|
||||
onLeave: dragTargetOnLeave,
|
||||
builder: (_, __, ___) => SizedBox.expand(),
|
||||
|
@ -309,12 +333,13 @@ class _BucketTaskCardState extends State<BucketTaskCard> with AutomaticKeepAlive
|
|||
|
||||
void _updateCardSize(BuildContext context) {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) setState(() {
|
||||
_cardSize = context.size;
|
||||
});
|
||||
if (mounted)
|
||||
setState(() {
|
||||
_cardSize = context.size;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => _dragging;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,23 +25,22 @@ class KanbanClass {
|
|||
Function _onViewTapped, _addItemDialog, notify;
|
||||
Duration _lastTaskDragUpdateAction = Duration.zero;
|
||||
|
||||
|
||||
Project _list;
|
||||
Map<int, BucketProps> _bucketProps = {};
|
||||
|
||||
|
||||
KanbanClass(this.context, this.notify, this._onViewTapped, this._addItemDialog, this._list) {
|
||||
KanbanClass(this.context, this.notify, this._onViewTapped,
|
||||
this._addItemDialog, this._list) {
|
||||
taskState = Provider.of<ProjectProvider>(context);
|
||||
}
|
||||
|
||||
|
||||
Widget kanbanView() {
|
||||
final deviceData = MediaQuery.of(context);
|
||||
final portrait = deviceData.orientation == Orientation.portrait;
|
||||
final bucketFraction = portrait ? 0.8 : 0.4;
|
||||
final bucketWidth = deviceData.size.width * bucketFraction;
|
||||
|
||||
if (_pageController == null || _pageController!.viewportFraction != bucketFraction)
|
||||
if (_pageController == null ||
|
||||
_pageController!.viewportFraction != bucketFraction)
|
||||
_pageController = PageController(viewportFraction: bucketFraction);
|
||||
|
||||
print(_list.doneBucketId);
|
||||
|
@ -170,14 +169,16 @@ class KanbanClass {
|
|||
),
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _setDoneBucket(BuildContext context, int bucketId) async {
|
||||
//setState(() {});
|
||||
_list = (await VikunjaGlobal.of(context).projectService.update(_list.copyWith(doneBucketId: bucketId)))!;
|
||||
_list = (await VikunjaGlobal.of(context)
|
||||
.projectService
|
||||
.update(_list.copyWith(doneBucketId: bucketId)))!;
|
||||
notify();
|
||||
}
|
||||
|
||||
Future<void> _addBucket(
|
||||
String title, BuildContext context) async {
|
||||
Future<void> _addBucket(String title, BuildContext context) async {
|
||||
final currentUser = VikunjaGlobal.of(context).currentUser;
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
|
@ -256,14 +257,12 @@ class KanbanClass {
|
|||
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;
|
||||
//});
|
||||
_bucketProps[bucket.id]!.bucketLength = bucket.tasks.length;
|
||||
_bucketProps[bucket.id]!.scrollable =
|
||||
_bucketProps[bucket.id]!.controller.position.maxScrollExtent > 0;
|
||||
_bucketProps[bucket.id]!.portrait = portrait;
|
||||
//});
|
||||
notify();
|
||||
|
||||
});
|
||||
if (_bucketProps[bucket.id]!.titleController.text.isEmpty)
|
||||
_bucketProps[bucket.id]!.titleController.text = bucket.title;
|
||||
|
@ -427,10 +426,11 @@ class KanbanClass {
|
|||
final screenSize = MediaQuery.of(context).size;
|
||||
const scrollDuration = Duration(milliseconds: 250);
|
||||
const scrollCurve = Curves.easeInOut;
|
||||
final updateAction = () { //setState(() =>
|
||||
final updateAction = () {
|
||||
//setState(() =>
|
||||
_lastTaskDragUpdateAction = details.sourceTimeStamp!;
|
||||
notify();
|
||||
};//);
|
||||
}; //);
|
||||
|
||||
if (details.globalPosition.dx < screenSize.width * 0.1) {
|
||||
// scroll left
|
||||
|
@ -502,8 +502,8 @@ class KanbanClass {
|
|||
if (bucket.tasks.length == 0)
|
||||
DragTarget<TaskData>(
|
||||
onWillAcceptWithDetails: (data) {
|
||||
/*setState(() =>*/ _bucketProps[bucket.id]!.taskDropSize =
|
||||
data.size;//);
|
||||
/*setState(() =>*/ _bucketProps[bucket.id]!
|
||||
.taskDropSize = data.data.size; //);
|
||||
notify();
|
||||
return true;
|
||||
},
|
||||
|
@ -511,23 +511,23 @@ class KanbanClass {
|
|||
Provider.of<ProjectProvider>(context, listen: false)
|
||||
.moveTaskToBucket(
|
||||
context: context,
|
||||
task: data.task,
|
||||
task: data.data.task,
|
||||
newBucketId: bucket.id,
|
||||
index: 0,
|
||||
)
|
||||
.then((_) => ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
'\'${data.task.title}\' was moved to \'${bucket.title}\' successfully!'),
|
||||
'\'${data.data.task.title}\' was moved to \'${bucket.title}\' successfully!'),
|
||||
)));
|
||||
|
||||
//setState(() =>
|
||||
_bucketProps[bucket.id]!.taskDropSize = null;//);
|
||||
_bucketProps[bucket.id]!.taskDropSize = null; //);
|
||||
notify();
|
||||
},
|
||||
onLeave: (_) {
|
||||
//setState(() =>
|
||||
_bucketProps[bucket.id]!.taskDropSize = null;//)
|
||||
_bucketProps[bucket.id]!.taskDropSize = null; //)
|
||||
notify();
|
||||
},
|
||||
builder: (_, __, ___) => SizedBox.expand(),
|
||||
|
@ -548,12 +548,7 @@ class KanbanClass {
|
|||
}
|
||||
|
||||
Future<void> loadBucketsForPage(int page) {
|
||||
return Provider.of<ProjectProvider>(context, listen: false).loadBuckets(
|
||||
context: context,
|
||||
listId: _list.id,
|
||||
page: page
|
||||
);
|
||||
return Provider.of<ProjectProvider>(context, listen: false)
|
||||
.loadBuckets(context: context, listId: _list.id, page: page);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import 'package:workmanager/workmanager.dart';
|
|||
import 'api/project.dart';
|
||||
import 'main.dart';
|
||||
|
||||
|
||||
class VikunjaGlobal extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Widget login;
|
||||
|
@ -50,7 +49,6 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
UserService? _newUserService;
|
||||
NotificationClass _notificationClass = NotificationClass();
|
||||
|
||||
|
||||
User? get currentUser => _currentUser;
|
||||
|
||||
Client get client => _client;
|
||||
|
@ -81,7 +79,6 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
|
||||
NotificationClass get notifications => _notificationClass;
|
||||
|
||||
|
||||
LabelService get labelService => new LabelAPIService(client);
|
||||
|
||||
LabelTaskService get labelTaskService => new LabelTaskAPIService(client);
|
||||
|
@ -89,21 +86,25 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
LabelTaskBulkAPIService get labelTaskBulkService =>
|
||||
new LabelTaskBulkAPIService(client);
|
||||
|
||||
|
||||
late String currentTimeZone;
|
||||
|
||||
void updateWorkmanagerDuration() {
|
||||
Workmanager().cancelAll().then((value) {
|
||||
settingsManager.getWorkmanagerDuration().then((duration)
|
||||
{
|
||||
if(duration.inMinutes > 0) {
|
||||
Workmanager().registerPeriodicTask(
|
||||
"update-tasks", "update-tasks", frequency: duration, constraints: Constraints(networkType: NetworkType.connected),
|
||||
initialDelay: Duration(seconds: 15), inputData: {"client_token": client.token, "client_base": client.base});
|
||||
settingsManager.getWorkmanagerDuration().then((duration) {
|
||||
if (duration.inMinutes > 0) {
|
||||
Workmanager().registerPeriodicTask("update-tasks", "update-tasks",
|
||||
frequency: duration,
|
||||
constraints: Constraints(networkType: NetworkType.connected),
|
||||
initialDelay: Duration(seconds: 15),
|
||||
inputData: {
|
||||
"client_token": client.token,
|
||||
"client_base": client.base
|
||||
});
|
||||
}
|
||||
|
||||
Workmanager().registerPeriodicTask(
|
||||
"refresh-token", "refresh-token", frequency: Duration(hours: 12), constraints: Constraints(networkType: NetworkType.connected),
|
||||
Workmanager().registerPeriodicTask("refresh-token", "refresh-token",
|
||||
frequency: Duration(hours: 12),
|
||||
constraints: Constraints(networkType: NetworkType.connected),
|
||||
initialDelay: Duration(seconds: 15));
|
||||
});
|
||||
});
|
||||
|
@ -113,13 +114,15 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
_client = Client(snackbarKey);
|
||||
settingsManager.getIgnoreCertificates().then((value) => client.reload_ignore_certs(value == "1"));
|
||||
settingsManager
|
||||
.getIgnoreCertificates()
|
||||
.then((value) => client.reloadIgnoreCerts(value == "1"));
|
||||
_newUserService = UserAPIService(client);
|
||||
_loadCurrentUser();
|
||||
tz.initializeTimeZones();
|
||||
notifications.notificationInitializer();
|
||||
settingsManager.getVersionNotifications().then((value) {
|
||||
if(value == "1") {
|
||||
if (value == "1") {
|
||||
versionChecker.postVersionCheckSnackbar();
|
||||
}
|
||||
});
|
||||
|
@ -152,17 +155,16 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
void logoutUser(BuildContext context) async {
|
||||
// _storage.deleteAll().then((_) {
|
||||
var userId = await _storage.read(key: "currentUser");
|
||||
await _storage.delete(key: userId!); //delete token
|
||||
await _storage.delete(key: "${userId}_base");
|
||||
setState(() {
|
||||
client.reset();
|
||||
_currentUser = null;
|
||||
});
|
||||
/* }).catchError((err) {
|
||||
var userId = await _storage.read(key: "currentUser");
|
||||
await _storage.delete(key: userId!); //delete token
|
||||
await _storage.delete(key: "${userId}_base");
|
||||
setState(() {
|
||||
client.reset();
|
||||
_currentUser = null;
|
||||
});
|
||||
/* }).catchError((err) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('An error occurred while logging out!'),
|
||||
));
|
||||
|
@ -191,13 +193,12 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
loadedCurrentUser = await UserAPIService(client).getCurrentUser();
|
||||
// load new token from server to avoid expiration
|
||||
String? newToken = await newUserService?.getToken();
|
||||
if(newToken != null) {
|
||||
if (newToken != null) {
|
||||
_storage.write(key: currentUser, value: newToken);
|
||||
client.configure(token: newToken);
|
||||
}
|
||||
|
||||
} on ApiException catch (e) {
|
||||
dev.log("Error code: " + e.errorCode.toString(),level: 1000);
|
||||
dev.log("Error code: " + e.errorCode.toString(), level: 1000);
|
||||
if (e.errorCode ~/ 100 == 4) {
|
||||
client.authenticated = false;
|
||||
if (e.errorCode == 401) {
|
||||
|
@ -227,7 +228,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
if (_loading) {
|
||||
return new Center(child: new CircularProgressIndicator());
|
||||
}
|
||||
if(client.authenticated) {
|
||||
if (client.authenticated) {
|
||||
notifications.scheduleDueNotifications(taskService);
|
||||
}
|
||||
return new VikunjaGlobalInherited(
|
||||
|
|
110
lib/main.dart
110
lib/main.dart
|
@ -35,7 +35,8 @@ class IgnoreCertHttpOverrides extends HttpOverrides {
|
|||
@pragma('vm:entry-point')
|
||||
void callbackDispatcher() {
|
||||
Workmanager().executeTask((task, inputData) async {
|
||||
print("Native called background task: $task"); //simpleTask will be emitted here.
|
||||
print(
|
||||
"Native called background task: $task"); //simpleTask will be emitted here.
|
||||
if (task == "update-tasks" && inputData != null) {
|
||||
Client client = Client(null,
|
||||
token: inputData["client_token"],
|
||||
|
@ -47,7 +48,7 @@ void callbackDispatcher() {
|
|||
.getIgnoreCertificates()
|
||||
.then((value) async {
|
||||
print("ignoring: $value");
|
||||
client.reload_ignore_certs(value == "1");
|
||||
client.reloadIgnoreCerts(value == "1");
|
||||
|
||||
TaskAPIService taskService = TaskAPIService(client);
|
||||
NotificationClass nc = NotificationClass();
|
||||
|
@ -56,7 +57,7 @@ void callbackDispatcher() {
|
|||
.scheduleDueNotifications(taskService)
|
||||
.then((value) => Future.value(true));
|
||||
});
|
||||
} else if( task == "refresh-token") {
|
||||
} else if (task == "refresh-token") {
|
||||
print("running refresh from workmanager");
|
||||
final FlutterSecureStorage _storage = new FlutterSecureStorage();
|
||||
|
||||
|
@ -66,25 +67,24 @@ void callbackDispatcher() {
|
|||
}
|
||||
var token = await _storage.read(key: currentUser);
|
||||
|
||||
|
||||
var base = await _storage.read(key: '${currentUser}_base');
|
||||
if (token == null || base == null) {
|
||||
return Future.value(true);
|
||||
}
|
||||
Client client = Client(null);
|
||||
client.configure(token: token, base: base, authenticated: true);
|
||||
// load new token from server to avoid expiration
|
||||
String? newToken = await UserAPIService(client).getToken();
|
||||
if(newToken != null) {
|
||||
_storage.write(key: currentUser, value: newToken);
|
||||
}
|
||||
// load new token from server to avoid expiration
|
||||
String? newToken = await UserAPIService(client).getToken();
|
||||
if (newToken != null) {
|
||||
_storage.write(key: currentUser, value: newToken);
|
||||
}
|
||||
return Future.value(true);
|
||||
|
||||
} else {
|
||||
return Future.value(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final globalSnackbarKey = GlobalKey<ScaffoldMessengerState>();
|
||||
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
|
@ -108,60 +108,62 @@ void main() async {
|
|||
key: UniqueKey(),
|
||||
)));
|
||||
}
|
||||
|
||||
final ValueNotifier<bool> updateTheme = ValueNotifier(false);
|
||||
|
||||
class VikunjaApp extends StatelessWidget {
|
||||
final Widget home;
|
||||
final GlobalKey<NavigatorState>? navkey;
|
||||
|
||||
const VikunjaApp({Key? key, required this.home, this.navkey}) : super(key: key);
|
||||
const VikunjaApp({Key? key, required this.home, this.navkey})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
SettingsManager manager = SettingsManager(new FlutterSecureStorage());
|
||||
|
||||
|
||||
return new ValueListenableBuilder(valueListenable: updateTheme, builder: (_,mode,__) {
|
||||
updateTheme.value = false;
|
||||
FlutterThemeMode themeMode = FlutterThemeMode.system;
|
||||
Future<ThemeData> theme = manager.getThemeMode().then((value) {
|
||||
themeMode = value;
|
||||
switch(value) {
|
||||
case FlutterThemeMode.dark:
|
||||
return buildVikunjaDarkTheme();
|
||||
case FlutterThemeMode.materialYouLight:
|
||||
return buildVikunjaMaterialLightTheme();
|
||||
case FlutterThemeMode.materialYouDark:
|
||||
return buildVikunjaMaterialDarkTheme();
|
||||
default:
|
||||
return buildVikunjaTheme();
|
||||
}
|
||||
|
||||
});
|
||||
return FutureBuilder<ThemeData>(
|
||||
future: theme,
|
||||
builder: (BuildContext context, AsyncSnapshot<ThemeData> data) {
|
||||
if(data.hasData) {
|
||||
return new DynamicColorBuilder(builder: (lightTheme, darkTheme)
|
||||
{
|
||||
ThemeData? themeData = data.data;
|
||||
if(themeMode == FlutterThemeMode.materialYouLight)
|
||||
themeData = themeData?.copyWith(colorScheme: lightTheme);
|
||||
else if(themeMode == FlutterThemeMode.materialYouDark)
|
||||
themeData = themeData?.copyWith(colorScheme: darkTheme);
|
||||
return MaterialApp(
|
||||
title: 'Vikunja',
|
||||
theme: themeData,
|
||||
scaffoldMessengerKey: globalSnackbarKey,
|
||||
navigatorKey: navkey,
|
||||
// <= this
|
||||
home: this.home,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
});});
|
||||
return new ValueListenableBuilder(
|
||||
valueListenable: updateTheme,
|
||||
builder: (_, mode, __) {
|
||||
updateTheme.value = false;
|
||||
FlutterThemeMode themeMode = FlutterThemeMode.system;
|
||||
Future<ThemeData> theme = manager.getThemeMode().then((value) {
|
||||
themeMode = value;
|
||||
switch (value) {
|
||||
case FlutterThemeMode.dark:
|
||||
return buildVikunjaDarkTheme();
|
||||
case FlutterThemeMode.materialYouLight:
|
||||
return buildVikunjaMaterialLightTheme();
|
||||
case FlutterThemeMode.materialYouDark:
|
||||
return buildVikunjaMaterialDarkTheme();
|
||||
default:
|
||||
return buildVikunjaTheme();
|
||||
}
|
||||
});
|
||||
return FutureBuilder<ThemeData>(
|
||||
future: theme,
|
||||
builder: (BuildContext context, AsyncSnapshot<ThemeData> data) {
|
||||
if (data.hasData) {
|
||||
return new DynamicColorBuilder(
|
||||
builder: (lightTheme, darkTheme) {
|
||||
ThemeData? themeData = data.data;
|
||||
if (themeMode == FlutterThemeMode.materialYouLight)
|
||||
themeData = themeData?.copyWith(colorScheme: lightTheme);
|
||||
else if (themeMode == FlutterThemeMode.materialYouDark)
|
||||
themeData = themeData?.copyWith(colorScheme: darkTheme);
|
||||
return MaterialApp(
|
||||
title: 'Vikunja',
|
||||
theme: themeData,
|
||||
scaffoldMessengerKey: globalSnackbarKey,
|
||||
navigatorKey: navkey,
|
||||
// <= this
|
||||
home: this.home,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import 'dart:math';
|
|||
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'as notifs;
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
|
||||
as notifs;
|
||||
import 'package:rxdart/subjects.dart' as rxSub;
|
||||
import 'package:vikunja_app/service/services.dart';
|
||||
|
||||
|
@ -19,62 +20,58 @@ class NotificationClass {
|
|||
late String currentTimeZone;
|
||||
notifs.NotificationAppLaunchDetails? notifLaunch;
|
||||
|
||||
notifs.FlutterLocalNotificationsPlugin get notificationsPlugin => new notifs.FlutterLocalNotificationsPlugin();
|
||||
|
||||
notifs.FlutterLocalNotificationsPlugin get notificationsPlugin =>
|
||||
new notifs.FlutterLocalNotificationsPlugin();
|
||||
|
||||
var androidSpecificsDueDate = notifs.AndroidNotificationDetails(
|
||||
"Vikunja1",
|
||||
"Due Date Notifications",
|
||||
"Vikunja1", "Due Date Notifications",
|
||||
channelDescription: "description",
|
||||
icon: 'vikunja_notification_logo',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
importance: notifs.Importance.high);
|
||||
var androidSpecificsReminders = notifs.AndroidNotificationDetails(
|
||||
"Vikunja2",
|
||||
"Reminder Notifications",
|
||||
"Vikunja2", "Reminder Notifications",
|
||||
channelDescription: "description",
|
||||
icon: 'vikunja_notification_logo',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
late notifs.IOSNotificationDetails iOSSpecifics;
|
||||
importance: notifs.Importance.high);
|
||||
late notifs.DarwinNotificationDetails iOSSpecifics;
|
||||
late notifs.NotificationDetails platformChannelSpecificsDueDate;
|
||||
late notifs.NotificationDetails platformChannelSpecificsReminders;
|
||||
|
||||
NotificationClass({this.id, this.body, this.payload, this.title});
|
||||
|
||||
final rxSub.BehaviorSubject<
|
||||
NotificationClass> didReceiveLocalNotificationSubject =
|
||||
rxSub.BehaviorSubject<NotificationClass>();
|
||||
final rxSub.BehaviorSubject<NotificationClass>
|
||||
didReceiveLocalNotificationSubject =
|
||||
rxSub.BehaviorSubject<NotificationClass>();
|
||||
final rxSub.BehaviorSubject<String> selectNotificationSubject =
|
||||
rxSub.BehaviorSubject<String>();
|
||||
rxSub.BehaviorSubject<String>();
|
||||
|
||||
Future<void> _initNotifications() async {
|
||||
var initializationSettingsAndroid =
|
||||
notifs.AndroidInitializationSettings('vikunja_logo');
|
||||
var initializationSettingsIOS = notifs.IOSInitializationSettings(
|
||||
notifs.AndroidInitializationSettings('vikunja_logo');
|
||||
var initializationSettingsIOS = notifs.DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
onDidReceiveLocalNotification:
|
||||
(int? id, String? title, String? body, String? payload) async {
|
||||
didReceiveLocalNotificationSubject
|
||||
.add(NotificationClass(
|
||||
didReceiveLocalNotificationSubject.add(NotificationClass(
|
||||
id: id, title: title, body: body, payload: payload));
|
||||
});
|
||||
var initializationSettings = notifs.InitializationSettings(
|
||||
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
|
||||
await notificationsPlugin.initialize(initializationSettings,
|
||||
onSelectNotification: (String? payload) async {
|
||||
if (payload != null) {
|
||||
print('notification payload: ' + payload);
|
||||
selectNotificationSubject.add(payload);
|
||||
}
|
||||
});
|
||||
onDidReceiveNotificationResponse:
|
||||
(notifs.NotificationResponse resp) async {
|
||||
if (payload != null) {
|
||||
print('notification payload: ' + resp.payload!);
|
||||
selectNotificationSubject.add(resp.payload!);
|
||||
}
|
||||
});
|
||||
print("Notifications initialised successfully");
|
||||
}
|
||||
|
||||
Future<void> notificationInitializer() async {
|
||||
iOSSpecifics = notifs.IOSNotificationDetails();
|
||||
iOSSpecifics = notifs.DarwinNotificationDetails();
|
||||
platformChannelSpecificsDueDate = notifs.NotificationDetails(
|
||||
android: androidSpecificsDueDate, iOS: iOSSpecifics);
|
||||
platformChannelSpecificsReminders = notifs.NotificationDetails(
|
||||
|
@ -86,20 +83,23 @@ class NotificationClass {
|
|||
return Future.value();
|
||||
}
|
||||
|
||||
Future<void> scheduleNotification(String title, String description,
|
||||
Future<void> scheduleNotification(
|
||||
String title,
|
||||
String description,
|
||||
notifs.FlutterLocalNotificationsPlugin notifsPlugin,
|
||||
DateTime scheduledTime, String currentTimeZone,
|
||||
notifs.NotificationDetails platformChannelSpecifics, {int? id}) async {
|
||||
if (id == null)
|
||||
id = Random().nextInt(1000000);
|
||||
DateTime scheduledTime,
|
||||
String currentTimeZone,
|
||||
notifs.NotificationDetails platformChannelSpecifics,
|
||||
{int? id}) async {
|
||||
if (id == null) id = Random().nextInt(1000000);
|
||||
// TODO: move to setup
|
||||
tz.TZDateTime time = tz.TZDateTime.from(
|
||||
scheduledTime, tz.getLocation(currentTimeZone));
|
||||
tz.TZDateTime time =
|
||||
tz.TZDateTime.from(scheduledTime, tz.getLocation(currentTimeZone));
|
||||
if (time.difference(tz.TZDateTime.now(tz.getLocation(currentTimeZone))) <
|
||||
Duration.zero)
|
||||
return;
|
||||
await notifsPlugin.zonedSchedule(id, title, description,
|
||||
time, platformChannelSpecifics, androidAllowWhileIdle: true,
|
||||
Duration.zero) return;
|
||||
await notifsPlugin.zonedSchedule(
|
||||
id, title, description, time, platformChannelSpecifics,
|
||||
androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation: notifs
|
||||
.UILocalNotificationDateInterpretation
|
||||
.wallClockTime); // This literally schedules the notification
|
||||
|
@ -110,30 +110,27 @@ class NotificationClass {
|
|||
"This is a test notification", platformChannelSpecificsReminders);
|
||||
}
|
||||
|
||||
|
||||
void requestIOSPermissions() {
|
||||
notificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
notifs.IOSFlutterLocalNotificationsPlugin>()
|
||||
notificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
notifs.IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Future<void> scheduleDueNotifications(TaskService taskService,
|
||||
{List<Task>? tasks}) async {
|
||||
if (tasks == null)
|
||||
tasks = await taskService.getAll();
|
||||
if (tasks == null) tasks = await taskService.getAll();
|
||||
if (tasks == null) {
|
||||
print("did not receive tasks on notification update");
|
||||
return;
|
||||
}
|
||||
await notificationsPlugin.cancelAll();
|
||||
for (final task in tasks) {
|
||||
if(task.done)
|
||||
continue;
|
||||
if (task.done) continue;
|
||||
for (final reminder in task.reminderDates) {
|
||||
scheduleNotification(
|
||||
"Reminder",
|
||||
|
@ -159,5 +156,4 @@ class NotificationClass {
|
|||
}
|
||||
print("notifications scheduled successfully");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ class LandingPageState extends State<LandingPage>
|
|||
static const platform = const MethodChannel('vikunja');
|
||||
|
||||
Future<void> _updateDefaultList() async {
|
||||
|
||||
return VikunjaGlobal.of(context).newUserService?.getCurrentUser().then((value) =>
|
||||
setState(() {
|
||||
defaultList = value?.settings?.default_project_id;
|
||||
} ),);
|
||||
return VikunjaGlobal.of(context).newUserService?.getCurrentUser().then(
|
||||
(value) => setState(() {
|
||||
defaultList = value?.settings?.default_project_id;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -55,7 +55,10 @@ class LandingPageState extends State<LandingPage>
|
|||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
VikunjaGlobal.of(context).settingsManager.getLandingPageOnlyDueDateTasks().then((value) => onlyDueDate = value);
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.getLandingPageOnlyDueDateTasks()
|
||||
.then((value) => onlyDueDate = value);
|
||||
}));
|
||||
super.initState();
|
||||
}
|
||||
|
@ -133,26 +136,29 @@ class LandingPageState extends State<LandingPage>
|
|||
PopupMenuButton(itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child:
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
bool newval = !onlyDueDate;
|
||||
VikunjaGlobal.of(context).settingsManager.setLandingPageOnlyDueDateTasks(newval).then((value) {
|
||||
setState(() {
|
||||
onlyDueDate = newval;
|
||||
_loadList(context);
|
||||
});
|
||||
});
|
||||
},
|
||||
child:
|
||||
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Text("Only show tasks with due date"),
|
||||
Checkbox(
|
||||
value: onlyDueDate,
|
||||
onChanged: (bool? value) { },
|
||||
)
|
||||
])))
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
bool newval = !onlyDueDate;
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setLandingPageOnlyDueDateTasks(newval)
|
||||
.then((value) {
|
||||
setState(() {
|
||||
onlyDueDate = newval;
|
||||
_loadList(context);
|
||||
});
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text("Only show tasks with due date"),
|
||||
Checkbox(
|
||||
value: onlyDueDate,
|
||||
onChanged: (bool? value) {},
|
||||
)
|
||||
])))
|
||||
];
|
||||
}),
|
||||
],
|
||||
|
@ -226,41 +232,46 @@ class LandingPageState extends State<LandingPage>
|
|||
.settingsManager
|
||||
.getLandingPageOnlyDueDateTasks()
|
||||
.then((showOnlyDueDateTasks) {
|
||||
VikunjaGlobalState global = VikunjaGlobal.of(context);
|
||||
Map<String, dynamic>? frontend_settings =
|
||||
global.currentUser?.settings?.frontend_settings;
|
||||
int? filterId = 0;
|
||||
if (frontend_settings != null) {
|
||||
if (frontend_settings["filter_id_used_on_overview"] != null)
|
||||
filterId = frontend_settings["filter_id_used_on_overview"];
|
||||
}
|
||||
if (filterId != null && filterId != 0) {
|
||||
return global.taskService.getAllByProject(filterId, {
|
||||
"sort_by": ["due_date", "id"],
|
||||
"order_by": ["asc", "desc"],
|
||||
}).then<Future<void>?>((response) =>
|
||||
_handleTaskList(response?.body, showOnlyDueDateTasks));
|
||||
}
|
||||
|
||||
VikunjaGlobalState global = VikunjaGlobal.of(context);
|
||||
Map<String, dynamic>? frontend_settings = global.currentUser?.settings?.frontend_settings;
|
||||
int? filterId = 0;
|
||||
if(frontend_settings != null) {
|
||||
if(frontend_settings["filter_id_used_on_overview"] != null)
|
||||
filterId = frontend_settings["filter_id_used_on_overview"];
|
||||
}
|
||||
if(filterId != null && filterId != 0) {
|
||||
return global.taskService.getAllByProject(filterId, {
|
||||
"sort_by": ["due_date", "id"],
|
||||
"order_by": ["asc", "desc"],
|
||||
}).then<Future<void>?>((response) => _handleTaskList(response?.body, showOnlyDueDateTasks));;
|
||||
}
|
||||
|
||||
return global.taskService
|
||||
.getByOptions(TaskServiceOptions(
|
||||
newOptions: [
|
||||
TaskServiceOption<TaskServiceOptionSortBy>("sort_by", ["due_date", "id"]),
|
||||
TaskServiceOption<TaskServiceOptionSortBy>("order_by", ["asc", "desc"]),
|
||||
TaskServiceOption<TaskServiceOptionFilterBy>("filter_by", "done"),
|
||||
TaskServiceOption<TaskServiceOptionFilterValue>("filter_value", "false"),
|
||||
TaskServiceOption<TaskServiceOptionFilterComparator>("filter_comparator", "equals"),
|
||||
TaskServiceOption<TaskServiceOptionFilterConcat>("filter_concat", "and"),
|
||||
],
|
||||
clearOther: true
|
||||
))
|
||||
.then<Future<void>?>((taskList) => _handleTaskList(taskList, showOnlyDueDateTasks));
|
||||
|
||||
});//.onError((error, stackTrace) {print("error");});
|
||||
return global.taskService
|
||||
.getByOptions(TaskServiceOptions(newOptions: [
|
||||
TaskServiceOption<TaskServiceOptionSortBy>(
|
||||
"sort_by", ["due_date", "id"]),
|
||||
TaskServiceOption<TaskServiceOptionSortBy>(
|
||||
"order_by", ["asc", "desc"]),
|
||||
TaskServiceOption<TaskServiceOptionFilterBy>("filter_by", "done"),
|
||||
TaskServiceOption<TaskServiceOptionFilterValue>(
|
||||
"filter_value", "false"),
|
||||
TaskServiceOption<TaskServiceOptionFilterComparator>(
|
||||
"filter_comparator", "equals"),
|
||||
TaskServiceOption<TaskServiceOptionFilterConcat>(
|
||||
"filter_concat", "and"),
|
||||
], clearOther: true))
|
||||
.then<Future<void>?>(
|
||||
(taskList) => _handleTaskList(taskList, showOnlyDueDateTasks));
|
||||
}); //.onError((error, stackTrace) {print("error");});
|
||||
}
|
||||
|
||||
Future<void> _handleTaskList(List<Task>? taskList, bool showOnlyDueDateTasks) {
|
||||
if(showOnlyDueDateTasks)
|
||||
taskList?.removeWhere((element) => element.dueDate == null || element.dueDate!.year == 0001);
|
||||
Future<void> _handleTaskList(
|
||||
List<Task>? taskList, bool showOnlyDueDateTasks) {
|
||||
if (showOnlyDueDateTasks)
|
||||
taskList?.removeWhere((element) =>
|
||||
element.dueDate == null || element.dueDate!.year == 0001);
|
||||
|
||||
if (taskList != null && taskList.isEmpty) {
|
||||
setState(() {
|
||||
|
|
|
@ -48,8 +48,8 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
@override
|
||||
void initState() {
|
||||
_repeatAfter = widget.task.repeatAfter;
|
||||
if(_repeatAfterType == null)
|
||||
_repeatAfterType = getRepeatAfterTypeFromDuration(_repeatAfter);
|
||||
if (_repeatAfterType == null)
|
||||
_repeatAfterType = getRepeatAfterTypeFromDuration(_repeatAfter);
|
||||
|
||||
_reminderDates = widget.task.reminderDates;
|
||||
for (var i = 0; i < _reminderDates.length; i++) {
|
||||
|
@ -86,25 +86,30 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () {showDialog(context: context, builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Delete Task'),
|
||||
content: Text('Are you sure you want to delete this task?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Delete'),
|
||||
onPressed: () {
|
||||
_delete(widget.task.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});},
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Delete Task'),
|
||||
content: Text(
|
||||
'Are you sure you want to delete this task?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Delete'),
|
||||
onPressed: () {
|
||||
_delete(widget.task.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -114,7 +119,8 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
key: _formKey,
|
||||
child: ListView(
|
||||
key: _listKey,
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, MediaQuery.of(context).size.height / 2),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16, 16, 16, MediaQuery.of(context).size.height / 2),
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
||||
|
@ -182,8 +188,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
flex: 2,
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.number,
|
||||
initialValue: getRepeatAfterValueFromDuration(
|
||||
_repeatAfter)?.toString(),
|
||||
initialValue:
|
||||
getRepeatAfterValueFromDuration(_repeatAfter)
|
||||
?.toString(),
|
||||
onSaved: (repeatAfter) => _repeatAfter =
|
||||
getDurationFromType(
|
||||
repeatAfter, _repeatAfterType),
|
||||
|
@ -200,8 +207,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
isExpanded: true,
|
||||
isDense: true,
|
||||
value: _repeatAfterType ??
|
||||
getRepeatAfterTypeFromDuration(
|
||||
_repeatAfter),
|
||||
getRepeatAfterTypeFromDuration(_repeatAfter),
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_repeatAfterType = newValue;
|
||||
|
@ -321,9 +327,11 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: 15,
|
||||
left: 2.0 + (IconTheme.of(context).size??0))),
|
||||
left: 2.0 + (IconTheme.of(context).size ?? 0))),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width - 80 - ((IconTheme.of(context).size ?? 0) * 2),
|
||||
width: MediaQuery.of(context).size.width -
|
||||
80 -
|
||||
((IconTheme.of(context).size ?? 0) * 2),
|
||||
child: TypeAheadField(
|
||||
builder: (builder, controller, focusnode) {
|
||||
return TextFormField(
|
||||
|
@ -427,18 +435,25 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
trailing: IconButton(
|
||||
icon: Icon(Icons.download),
|
||||
onPressed: () async {
|
||||
String url = VikunjaGlobal.of(context).client.base;
|
||||
url += '/tasks/${widget.task.id}/attachments/${widget.task.attachments[index].id}';
|
||||
String url =
|
||||
VikunjaGlobal.of(context).client.base;
|
||||
url +=
|
||||
'/tasks/${widget.task.id}/attachments/${widget.task.attachments[index].id}';
|
||||
print(url);
|
||||
final taskId = await FlutterDownloader.enqueue(
|
||||
url: url,
|
||||
fileName: widget.task.attachments[index].file.name,
|
||||
headers: VikunjaGlobal.of(context).client.headers, // optional: header send with url (auth token etc)
|
||||
fileName:
|
||||
widget.task.attachments[index].file.name,
|
||||
headers: VikunjaGlobal.of(context)
|
||||
.client
|
||||
.headers, // optional: header send with url (auth token etc)
|
||||
savedDir: '/storage/emulated/0/Download/',
|
||||
showNotification: true, // show download progress in status bar (for Android)
|
||||
openFileFromNotification: true, // click on notification to open downloaded file (for Android)
|
||||
showNotification:
|
||||
true, // show download progress in status bar (for Android)
|
||||
openFileFromNotification:
|
||||
true, // click on notification to open downloaded file (for Android)
|
||||
);
|
||||
if(taskId == null) return;
|
||||
if (taskId == null) return;
|
||||
FlutterDownloader.open(taskId: taskId);
|
||||
},
|
||||
),
|
||||
|
@ -594,8 +609,6 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
_onColorEdit() {
|
||||
_pickerColor = _resetColor || (_color ?? widget.task.color) == null
|
||||
? Colors.black
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'package:after_layout/after_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:vikunja_app/pages/project/project_task_list.dart';
|
||||
|
@ -16,14 +15,9 @@ class ProjectOverviewPage extends StatefulWidget {
|
|||
class _ProjectOverviewPageState extends State<ProjectOverviewPage>
|
||||
with AfterLayoutMixin<ProjectOverviewPage> {
|
||||
List<Project> _projects = [];
|
||||
int _selectedDrawerIndex = -2, _previousDrawerIndex = -2;
|
||||
int _selectedDrawerIndex = -2;
|
||||
bool _loading = true;
|
||||
|
||||
Project? get _currentProject =>
|
||||
_selectedDrawerIndex >= -1 && _selectedDrawerIndex < _projects.length
|
||||
? _projects[_selectedDrawerIndex]
|
||||
: null;
|
||||
|
||||
@override
|
||||
void afterFirstLayout(BuildContext context) {
|
||||
_loadProjects();
|
||||
|
@ -37,9 +31,9 @@ class _ProjectOverviewPageState extends State<ProjectOverviewPage>
|
|||
bool expanded = expandedList.contains(project.id);
|
||||
Widget icon;
|
||||
|
||||
List<Widget>? children = addProjectChildren(project, level+1);
|
||||
List<Widget>? children = addProjectChildren(project, level + 1);
|
||||
bool no_children = children.length == 0;
|
||||
if(no_children) {
|
||||
if (no_children) {
|
||||
icon = Icon(Icons.list);
|
||||
} else {
|
||||
if (expanded) {
|
||||
|
@ -50,34 +44,34 @@ class _ProjectOverviewPageState extends State<ProjectOverviewPage>
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return Column(children: [
|
||||
ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
openList(context, project);
|
||||
});
|
||||
},
|
||||
contentPadding: insets,
|
||||
return Column(children: [
|
||||
ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
openList(context, project);
|
||||
});
|
||||
},
|
||||
contentPadding: insets,
|
||||
leading: IconButton(
|
||||
disabledColor: Theme.of(context).unselectedWidgetColor,
|
||||
icon: icon,
|
||||
onPressed: !no_children ? () {
|
||||
setState(() {
|
||||
if (expanded)
|
||||
expandedList.remove(project.id);
|
||||
else
|
||||
expandedList.add(project.id);
|
||||
});
|
||||
} : null,
|
||||
onPressed: !no_children
|
||||
? () {
|
||||
setState(() {
|
||||
if (expanded)
|
||||
expandedList.remove(project.id);
|
||||
else
|
||||
expandedList.add(project.id);
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
title: new Text(project.title),
|
||||
//onTap: () => _onSelectItem(i),
|
||||
),
|
||||
...?children
|
||||
]);
|
||||
}
|
||||
|
||||
...?children
|
||||
]);
|
||||
}
|
||||
|
||||
List<Widget> addProjectChildren(Project project, level) {
|
||||
Iterable<Project> children =
|
||||
|
@ -120,7 +114,6 @@ class _ProjectOverviewPageState extends State<ProjectOverviewPage>
|
|||
.toList()),
|
||||
onRefresh: _loadProjects,
|
||||
),
|
||||
|
||||
appBar: AppBar(
|
||||
title: Text("Projects"),
|
||||
),
|
||||
|
@ -136,8 +129,6 @@ class _ProjectOverviewPageState extends State<ProjectOverviewPage>
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
_addProjectDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
|
@ -8,7 +8,6 @@ import '../models/project.dart';
|
|||
import '../models/user.dart';
|
||||
import '../service/services.dart';
|
||||
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => SettingsPageState();
|
||||
|
@ -25,7 +24,6 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
FlutterThemeMode? themeMode;
|
||||
User? currentUser;
|
||||
|
||||
|
||||
void init() {
|
||||
durationTextController = TextEditingController();
|
||||
|
||||
|
@ -51,20 +49,21 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
.getCurrentVersionTag()
|
||||
.then((value) => setState(() => versionTag = value));
|
||||
|
||||
VikunjaGlobal.of(context).settingsManager.getWorkmanagerDuration().then(
|
||||
(value) => setState(
|
||||
() => durationTextController.text = (value.inMinutes.toString())));
|
||||
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.getWorkmanagerDuration()
|
||||
.then((value) => setState(() => durationTextController.text = (value.inMinutes.toString())));
|
||||
|
||||
VikunjaGlobal.of(context).settingsManager.getThemeMode().then((value) => setState(() => themeMode = value));
|
||||
.getThemeMode()
|
||||
.then((value) => setState(() => themeMode = value));
|
||||
|
||||
VikunjaGlobal.of(context).newUserService?.getCurrentUser().then((value) => {
|
||||
setState(() {
|
||||
currentUser = value!;
|
||||
defaultProject = value.settings?.default_project_id;
|
||||
} ),
|
||||
});
|
||||
|
||||
setState(() {
|
||||
currentUser = value!;
|
||||
defaultProject = value.settings?.default_project_id;
|
||||
}),
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
@ -75,18 +74,22 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
if (!initialized) init();
|
||||
return new Scaffold(
|
||||
appBar: AppBar(title: Text("Settings"),),
|
||||
|
||||
appBar: AppBar(
|
||||
title: Text("Settings"),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
UserAccountsDrawerHeader(
|
||||
accountName: currentUser != null ? Text(currentUser!.username) : null,
|
||||
accountName:
|
||||
currentUser != null ? Text(currentUser!.username) : null,
|
||||
accountEmail: currentUser != null ? Text(currentUser!.name) : null,
|
||||
currentAccountPicture: currentUser == null
|
||||
? null
|
||||
: CircleAvatar(
|
||||
backgroundImage: (currentUser?.username != "") ? NetworkImage(currentUser!.avatarUrl(context)) : null,
|
||||
),
|
||||
backgroundImage: (currentUser?.username != "")
|
||||
? NetworkImage(currentUser!.avatarUrl(context))
|
||||
: null,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage("assets/graphics/hypnotize.png"),
|
||||
|
@ -109,11 +112,17 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
child: Text(e.title), value: e.id))
|
||||
.toList()
|
||||
],
|
||||
value: projectList?.firstWhereOrNull((element) => element.id == defaultProject) != null ? defaultProject : null,
|
||||
value: projectList?.firstWhereOrNull(
|
||||
(element) => element.id == defaultProject) !=
|
||||
null
|
||||
? defaultProject
|
||||
: null,
|
||||
onChanged: (int? value) {
|
||||
setState(() => defaultProject = value);
|
||||
global.newUserService?.setCurrentUserSettings(
|
||||
currentUser!.settings!.copyWith(default_project_id: value)).then((value) => currentUser!.settings = value);
|
||||
global.newUserService
|
||||
?.setCurrentUserSettings(currentUser!.settings!
|
||||
.copyWith(default_project_id: value))
|
||||
.then((value) => currentUser!.settings = value);
|
||||
//VikunjaGlobal.of(context).userManager.setDefaultList(value);
|
||||
},
|
||||
),
|
||||
|
@ -149,9 +158,7 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
],
|
||||
value: themeMode,
|
||||
onChanged: (FlutterThemeMode? value) {
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setThemeMode(value!);
|
||||
VikunjaGlobal.of(context).settingsManager.setThemeMode(value!);
|
||||
setState(() => themeMode = value);
|
||||
updateTheme.value = true;
|
||||
},
|
||||
|
@ -164,27 +171,30 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
value: ignoreCertificates,
|
||||
onChanged: (value) {
|
||||
setState(() => ignoreCertificates = value);
|
||||
VikunjaGlobal.of(context).client.reload_ignore_certs(value);
|
||||
VikunjaGlobal.of(context).client.reloadIgnoreCerts(value);
|
||||
})
|
||||
: ListTile(title: Text("...")),
|
||||
Divider(),
|
||||
Padding(padding: EdgeInsets.only(left: 15, right: 15),
|
||||
Divider(),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 15, right: 15),
|
||||
child: Row(children: [
|
||||
Flexible(
|
||||
child: TextField(
|
||||
controller: durationTextController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Background Refresh Interval (minutes): ',
|
||||
helperText: 'Minimum: 15, Set limit of 0 for no refresh',
|
||||
),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () => VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setWorkmanagerDuration(Duration(
|
||||
minutes: int.parse(durationTextController.text))).then((value) => VikunjaGlobal.of(context).updateWorkmanagerDuration()),
|
||||
child: Text("Save")),
|
||||
])),
|
||||
Flexible(
|
||||
child: TextField(
|
||||
controller: durationTextController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Background Refresh Interval (minutes): ',
|
||||
helperText: 'Minimum: 15, Set limit of 0 for no refresh',
|
||||
),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () => VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setWorkmanagerDuration(Duration(
|
||||
minutes: int.parse(durationTextController.text)))
|
||||
.then((value) => VikunjaGlobal.of(context)
|
||||
.updateWorkmanagerDuration()),
|
||||
child: Text("Save")),
|
||||
])),
|
||||
Divider(),
|
||||
getVersionNotifications != null
|
||||
? CheckboxListTile(
|
||||
|
|
|
@ -33,11 +33,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
|
||||
final _serverSuggestionController = SuggestionsController();
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(Duration.zero, () async{
|
||||
Future.delayed(Duration.zero, () async {
|
||||
if (VikunjaGlobal.of(context).expired) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text("Login has expired. Please reenter your details!")));
|
||||
|
@ -48,10 +47,16 @@ class _LoginPageState extends State<LoginPage> {
|
|||
});
|
||||
}
|
||||
final client = VikunjaGlobal.of(context).client;
|
||||
await VikunjaGlobal.of(context).settingsManager.getIgnoreCertificates().then(
|
||||
(value) => setState(() => client.ignoreCertificates = value == "1"));
|
||||
await VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.getIgnoreCertificates()
|
||||
.then((value) =>
|
||||
setState(() => client.ignoreCertificates = value == "1"));
|
||||
|
||||
await VikunjaGlobal.of(context).settingsManager.getPastServers().then((value) {
|
||||
await VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.getPastServers()
|
||||
.then((value) {
|
||||
print(value);
|
||||
if (value != null) setState(() => pastServers = value);
|
||||
});
|
||||
|
@ -101,11 +106,11 @@ class _LoginPageState extends State<LoginPage> {
|
|||
enabled: !_loading,
|
||||
validator: (address) {
|
||||
return (isUrl(address) ||
|
||||
address != null ||
|
||||
address!.isEmpty)
|
||||
? null
|
||||
: 'Invalid URL';
|
||||
},
|
||||
address != null ||
|
||||
address!.isEmpty)
|
||||
? null
|
||||
: 'Invalid URL';
|
||||
},
|
||||
decoration: new InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Server Address'),
|
||||
|
@ -120,34 +125,42 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),*/
|
||||
onSelected: (suggestion) {
|
||||
_serverController.text = suggestion;
|
||||
setState(() => _serverController.text = suggestion);
|
||||
setState(
|
||||
() => _serverController.text = suggestion);
|
||||
},
|
||||
itemBuilder: (BuildContext context, Object? itemData) {
|
||||
itemBuilder:
|
||||
(BuildContext context, Object? itemData) {
|
||||
return Card(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
child:
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(itemData.toString()),
|
||||
IconButton(onPressed: () {
|
||||
setState(() {
|
||||
pastServers.remove(itemData.toString());
|
||||
//_serverSuggestionController.suggestionsBox?.close();
|
||||
VikunjaGlobal.of(context).settingsManager.setPastServers(pastServers);
|
||||
|
||||
});
|
||||
}, icon: Icon(Icons.clear))
|
||||
],
|
||||
))
|
||||
);
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(itemData.toString()),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
pastServers.remove(
|
||||
itemData.toString());
|
||||
//_serverSuggestionController.suggestionsBox?.close();
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setPastServers(
|
||||
pastServers);
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.clear))
|
||||
],
|
||||
)));
|
||||
},
|
||||
suggestionsCallback: (String pattern) {
|
||||
List<String> matches = <String>[];
|
||||
matches.addAll(pastServers);
|
||||
matches.retainWhere((s){
|
||||
return s.toLowerCase().contains(pattern.toLowerCase());
|
||||
matches.retainWhere((s) {
|
||||
return s
|
||||
.toLowerCase()
|
||||
.contains(pattern.toLowerCase());
|
||||
});
|
||||
return matches;
|
||||
},
|
||||
|
@ -246,20 +259,18 @@ class _LoginPageState extends State<LoginPage> {
|
|||
}
|
||||
},
|
||||
child: VikunjaButtonText("Login with Frontend"))),
|
||||
CheckboxListTile(
|
||||
title: Text("Ignore Certificates"),
|
||||
value: client.ignoreCertificates,
|
||||
onChanged: (value) {
|
||||
setState(() =>
|
||||
client.reload_ignore_certs(value ?? false));
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setIgnoreCertificates(value ?? false);
|
||||
VikunjaGlobal.of(context)
|
||||
.client
|
||||
.ignoreCertificates = value ?? false;
|
||||
})
|
||||
|
||||
CheckboxListTile(
|
||||
title: Text("Ignore Certificates"),
|
||||
value: client.ignoreCertificates,
|
||||
onChanged: (value) {
|
||||
setState(
|
||||
() => client.reloadIgnoreCerts(value ?? false));
|
||||
VikunjaGlobal.of(context)
|
||||
.settingsManager
|
||||
.setIgnoreCertificates(value ?? false);
|
||||
VikunjaGlobal.of(context).client.ignoreCertificates =
|
||||
value ?? false;
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -276,7 +287,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
String _password = _passwordController.text;
|
||||
if (_server.isEmpty) return;
|
||||
|
||||
if(!pastServers.contains(_server)) pastServers.add(_server);
|
||||
if (!pastServers.contains(_server)) pastServers.add(_server);
|
||||
await VikunjaGlobal.of(context).settingsManager.setPastServers(pastServers);
|
||||
|
||||
setState(() => _loading = true);
|
||||
|
@ -285,8 +296,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
vGlobal.client.showSnackBar = false;
|
||||
vGlobal.client.configure(base: _server);
|
||||
Server? info = await vGlobal.serverService.getInfo();
|
||||
if (info == null)
|
||||
throw Exception("Getting server info failed");
|
||||
if (info == null) throw Exception("Getting server info failed");
|
||||
|
||||
UserTokenPair newUser;
|
||||
|
||||
|
@ -297,27 +307,27 @@ class _LoginPageState extends State<LoginPage> {
|
|||
TextEditingController totpController = TextEditingController();
|
||||
bool dismissed = true;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => new AlertDialog(
|
||||
title: Text("Enter One Time Passcode"),
|
||||
content: TextField(
|
||||
controller: totpController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
dismissed = false;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("Login"))
|
||||
],
|
||||
),
|
||||
context: context,
|
||||
builder: (context) => new AlertDialog(
|
||||
title: Text("Enter One Time Passcode"),
|
||||
content: TextField(
|
||||
controller: totpController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
dismissed = false;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("Login"))
|
||||
],
|
||||
),
|
||||
);
|
||||
if(!dismissed) {
|
||||
if (!dismissed) {
|
||||
newUser = await vGlobal.newUserService!.login(_username, _password,
|
||||
rememberMe: this._rememberMe, totp: totpController.text);
|
||||
} else {
|
||||
|
|
204
pubspec.lock
204
pubspec.lock
|
@ -122,21 +122,21 @@ packages:
|
|||
source: hosted
|
||||
version: "2.0.3"
|
||||
chewie:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: chewie
|
||||
sha256: "745e81e84c6d7f3835f89f85bb49771c0a66099e4caf8f8e9e9a372bc66fb2c1"
|
||||
sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.7.5"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
|
||||
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
version: "0.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -253,10 +253,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
version: "7.0.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -298,10 +298,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_keyboard_visibility
|
||||
sha256: "183ef857869a790aaff0219327a31783017fcc54b895fcdda1909645bb6ab965"
|
||||
sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.2"
|
||||
version: "6.0.0"
|
||||
flutter_keyboard_visibility_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -346,42 +346,42 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: a9de6706cd844668beac27c0aed5910fa0534832b3c2cad61a5fd977fce82a5d
|
||||
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0"
|
||||
version: "0.13.1"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64"
|
||||
sha256: f9a05409385b77b06c18f200a41c7c2711ebf7415669350bb0f8474c07bd40d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.9.1"
|
||||
version: "17.0.0"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8
|
||||
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "4.0.0+1"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a"
|
||||
sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "7.0.0+1"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f"
|
||||
sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.0"
|
||||
version: "9.0.0"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -418,18 +418,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255"
|
||||
sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "3.0.0"
|
||||
flutter_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
|
||||
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
version: "2.0.10+1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -447,10 +447,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_typeahead
|
||||
sha256: "1f6b248bb4f3ebb4cf1ee0354aa23c77be457fb2d26d6847ecc33a917f65e58e"
|
||||
sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
version: "5.2.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -548,10 +548,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
version: "1.2.1"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -572,18 +572,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
|
||||
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
version: "4.1.7"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
version: "0.19.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -737,13 +737,13 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745"
|
||||
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "4.2.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -828,50 +828,58 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
|
||||
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.4.5"
|
||||
version: "11.3.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
|
||||
sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.6"
|
||||
version: "12.0.5"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
version: "9.4.4"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
||||
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.12.0"
|
||||
version: "4.2.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -892,10 +900,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pointer_interceptor
|
||||
sha256: adf7a637f97c077041d36801b43be08559fd4322d2127b3f20bb7be1b9eebc22
|
||||
sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+7"
|
||||
version: "0.10.1"
|
||||
pointer_interceptor_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointer_interceptor_ios
|
||||
sha256: "2e73c39452830adc4695757130676a39412a3b7f3c34e3f752791b5384770877"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0+2"
|
||||
pointer_interceptor_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointer_interceptor_platform_interface
|
||||
sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0+1"
|
||||
pointer_interceptor_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointer_interceptor_web
|
||||
sha256: a6237528b46c411d8d55cdfad8fcb3269fc4cbb26060b14bff94879165887d1e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.2"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -912,14 +944,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1121,10 +1145,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: timezone
|
||||
sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e"
|
||||
sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.9.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1209,26 +1233,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752"
|
||||
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.10+1"
|
||||
version: "1.1.11+1"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33
|
||||
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.10+1"
|
||||
version: "1.1.11+1"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a"
|
||||
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.10+1"
|
||||
version: "1.1.11+1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1285,46 +1309,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.0"
|
||||
wakelock:
|
||||
wakelock_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock
|
||||
sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db"
|
||||
name: wakelock_plus
|
||||
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.2"
|
||||
wakelock_macos:
|
||||
version: "1.1.4"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_macos
|
||||
sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd"
|
||||
name: wakelock_plus_platform_interface
|
||||
sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
wakelock_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_platform_interface
|
||||
sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
wakelock_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_web
|
||||
sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
wakelock_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_windows
|
||||
sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
version: "1.1.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1393,10 +1393,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
|
||||
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "5.3.0"
|
||||
workmanager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1409,18 +1409,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0+3"
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
55
pubspec.yaml
55
pubspec.yaml
|
@ -9,44 +9,41 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.5
|
||||
http: ^0.13.5
|
||||
cupertino_icons: ^1.0.6
|
||||
http: ^1.2.1
|
||||
after_layout: ^1.2.0
|
||||
intl: ^0.17.0
|
||||
flutter_local_notifications: ^9.8.0+1
|
||||
rxdart: ^0.27.5
|
||||
flutter_timezone: ^1.0.4
|
||||
flutter_secure_storage: ^8.0.0
|
||||
intl: ^0.19.0
|
||||
flutter_local_notifications: ^17.0.0
|
||||
rxdart: ^0.27.7
|
||||
flutter_timezone: ^1.0.8
|
||||
flutter_secure_storage: ^9.0.0
|
||||
datetime_picker_formfield: ^2.0.1
|
||||
flutter_typeahead: ^5.0.1
|
||||
build: ^2.3.0
|
||||
json_serializable: ^6.3.1
|
||||
petitparser: ^5.0.0
|
||||
provider: ^6.0.3
|
||||
webview_flutter: ^4.4.3
|
||||
flutter_typeahead: ^5.2.0
|
||||
build: ^2.4.1
|
||||
json_serializable: ^6.7.1
|
||||
petitparser: ^6.0.2
|
||||
provider: ^6.1.2
|
||||
webview_flutter: ^4.7.0
|
||||
flutter_colorpicker: ^1.0.3
|
||||
flutter_keyboard_visibility: ^5.4.2
|
||||
dotted_border: ^2.0.0+2
|
||||
package_info_plus: ^3.0.2
|
||||
url_launcher: ^6.1.7
|
||||
workmanager: ^0.5.1
|
||||
permission_handler: ^10.2.0
|
||||
dynamic_color: ^1.6.6
|
||||
chewie: ^1.5.0
|
||||
flutter_widget_from_html: ^0.14.10
|
||||
flutter_keyboard_visibility: ^6.0.0
|
||||
dotted_border: ^2.1.0
|
||||
url_launcher: ^6.2.5
|
||||
workmanager: ^0.5.2
|
||||
permission_handler: ^11.3.0
|
||||
dynamic_color: ^1.7.0
|
||||
flutter_widget_from_html: ^0.14.11
|
||||
flutter_downloader: ^1.11.6
|
||||
|
||||
|
||||
meta: any
|
||||
timezone: any
|
||||
json_annotation: any
|
||||
collection: any
|
||||
meta: ^1.11.0
|
||||
timezone: ^0.9.2
|
||||
json_annotation: ^4.8.1
|
||||
collection: ^1.18.0
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
version: any
|
||||
test: ^1.21.1
|
||||
flutter_launcher_icons: ^0.10.0
|
||||
test: ^1.24.9
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
|
||||
flutter_icons:
|
||||
image_path: "assets/vikunja_logo.png"
|
||||
|
|
Loading…
Reference in New Issue
Block a user