2018-09-17 13:35:57 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
2022-04-20 20:57:21 +00:00
|
|
|
import 'dart:core';
|
2022-05-08 08:16:06 +00:00
|
|
|
import 'dart:developer';
|
|
|
|
import 'package:flutter/material.dart';
|
2018-09-17 13:35:57 +00:00
|
|
|
import 'package:http/http.dart' as http;
|
2021-06-04 09:34:25 +00:00
|
|
|
import 'package:vikunja_app/api/response.dart';
|
|
|
|
import 'package:vikunja_app/components/string_extension.dart';
|
2022-05-07 15:18:03 +00:00
|
|
|
import 'package:vikunja_app/global.dart';
|
2018-09-17 13:35:57 +00:00
|
|
|
|
2018-09-16 19:47:33 +00:00
|
|
|
class Client {
|
2022-05-07 15:18:03 +00:00
|
|
|
VikunjaGlobalState global;
|
2018-09-17 13:35:57 +00:00
|
|
|
final JsonDecoder _decoder = new JsonDecoder();
|
|
|
|
final JsonEncoder _encoder = new JsonEncoder();
|
2022-05-07 15:18:03 +00:00
|
|
|
String _token;
|
|
|
|
String _base;
|
2022-04-25 21:47:40 +00:00
|
|
|
bool authenticated;
|
2018-09-17 13:35:57 +00:00
|
|
|
|
|
|
|
String get base => _base;
|
2022-04-28 14:59:41 +00:00
|
|
|
String get token => _token;
|
2018-09-16 19:47:33 +00:00
|
|
|
|
2022-05-07 15:18:03 +00:00
|
|
|
//Client(this._token, String base, {this.authenticated = true})
|
|
|
|
// : _base = base.endsWith('/api/v1') ? base : '$base/api/v1';
|
2018-09-16 19:47:33 +00:00
|
|
|
|
|
|
|
bool operator ==(dynamic otherClient) {
|
|
|
|
return otherClient._token == _token;
|
|
|
|
}
|
|
|
|
|
2022-05-07 15:18:03 +00:00
|
|
|
Client(this.global, {String token, String base, bool authenticated = false})
|
|
|
|
{ configure(token: token, base: base, authenticated: authenticated);}
|
|
|
|
|
2018-09-17 13:35:57 +00:00
|
|
|
get _headers => {
|
|
|
|
'Authorization': _token != null ? 'Bearer $_token' : '',
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
};
|
|
|
|
|
2022-05-08 08:16:06 +00:00
|
|
|
@override
|
|
|
|
int get hashCode => _token.hashCode;
|
|
|
|
|
2022-05-07 15:18:03 +00:00
|
|
|
void configure({String token, String base, bool authenticated}) {
|
|
|
|
if(token != null)
|
|
|
|
_token = token;
|
|
|
|
if(base != null)
|
|
|
|
_base = base.endsWith('/api/v1') ? base : '$base/api/v1';
|
|
|
|
if(authenticated != null)
|
|
|
|
this.authenticated = authenticated;
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset() {
|
|
|
|
_token = _base = null;
|
|
|
|
authenticated = false;
|
|
|
|
}
|
|
|
|
|
2021-06-04 09:34:25 +00:00
|
|
|
Future<Response> get(String url,
|
|
|
|
[Map<String, List<String>> queryParameters]) {
|
|
|
|
// TODO: This could be moved to a seperate function
|
|
|
|
var uri = Uri.parse('${this.base}$url');
|
|
|
|
// Because these are all final values, we can't just add the queryParameters and must instead build a new Uri Object every time this method is called.
|
|
|
|
var newUri = Uri(
|
|
|
|
scheme: uri.scheme,
|
|
|
|
userInfo: uri.userInfo,
|
|
|
|
host: uri.host,
|
|
|
|
port: uri.port,
|
|
|
|
path: uri.path,
|
|
|
|
query: uri.query,
|
|
|
|
queryParameters: queryParameters,
|
|
|
|
// Because dart takes a Map<String, String> here, it is only possible to sort by one parameter while the api supports n parameters.
|
|
|
|
fragment: uri.fragment);
|
2022-05-08 08:16:06 +00:00
|
|
|
return http.get(newUri, headers: _headers)
|
|
|
|
.then(_handleResponse, onError: _handleError);
|
|
|
|
}
|
2018-09-17 13:35:57 +00:00
|
|
|
|
2021-06-04 09:34:25 +00:00
|
|
|
Future<Response> delete(String url) {
|
2018-09-17 16:16:50 +00:00
|
|
|
return http
|
2021-12-21 11:22:17 +00:00
|
|
|
.delete(
|
|
|
|
'${this.base}$url'.toUri(),
|
|
|
|
headers: _headers,
|
|
|
|
)
|
2022-05-08 08:16:06 +00:00
|
|
|
.then(_handleResponse, onError: _handleError);
|
2018-09-17 16:16:50 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 09:34:25 +00:00
|
|
|
Future<Response> post(String url, {dynamic body}) {
|
2018-09-17 13:35:57 +00:00
|
|
|
return http
|
2021-12-21 11:22:17 +00:00
|
|
|
.post(
|
|
|
|
'${this.base}$url'.toUri(),
|
|
|
|
headers: _headers,
|
|
|
|
body: _encoder.convert(body),
|
|
|
|
)
|
2022-05-08 08:16:06 +00:00
|
|
|
.then(_handleResponse, onError: _handleError);
|
2018-09-17 13:35:57 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 09:34:25 +00:00
|
|
|
Future<Response> put(String url, {dynamic body}) {
|
2018-09-17 16:16:50 +00:00
|
|
|
return http
|
2021-12-21 11:22:17 +00:00
|
|
|
.put(
|
|
|
|
'${this.base}$url'.toUri(),
|
|
|
|
headers: _headers,
|
|
|
|
body: _encoder.convert(body),
|
|
|
|
)
|
2022-05-08 08:16:06 +00:00
|
|
|
.then(_handleResponse, onError: _handleError);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _handleError(dynamic e) {
|
|
|
|
log(e.toString());
|
|
|
|
ScaffoldMessenger.of(global.context).showSnackBar(
|
|
|
|
SnackBar(content: Text("Error code on request: " + e.toString())));
|
2018-09-17 16:16:50 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 09:34:25 +00:00
|
|
|
Response _handleResponse(http.Response response) {
|
2018-09-17 13:35:57 +00:00
|
|
|
if (response.statusCode < 200 ||
|
2020-06-17 16:51:23 +00:00
|
|
|
response.statusCode >= 400 ||
|
2018-09-17 13:35:57 +00:00
|
|
|
json == null) {
|
2020-06-17 16:51:23 +00:00
|
|
|
if (response.statusCode ~/ 100 == 4) {
|
|
|
|
Map<String, dynamic> error = _decoder.convert(response.body);
|
|
|
|
throw new InvalidRequestApiException(
|
|
|
|
response.statusCode,
|
|
|
|
response.request.url.toString(),
|
|
|
|
error["message"] ?? "Unknown Error");
|
|
|
|
}
|
2022-05-08 08:16:06 +00:00
|
|
|
//ScaffoldMessenger.of(global.context).showSnackBar(SnackBar(content: Text("Error code "+response.statusCode.toString()+" on request: " + error["message"])));
|
2018-09-17 13:35:57 +00:00
|
|
|
throw new ApiException(
|
|
|
|
response.statusCode, response.request.url.toString());
|
|
|
|
}
|
2021-06-04 09:34:25 +00:00
|
|
|
return Response(
|
2021-12-21 11:22:17 +00:00
|
|
|
_decoder.convert(response.body), response.statusCode, response.headers);
|
2018-09-17 13:35:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 16:51:23 +00:00
|
|
|
class InvalidRequestApiException extends ApiException {
|
|
|
|
final String message;
|
2021-06-04 09:34:25 +00:00
|
|
|
|
2020-06-17 16:51:23 +00:00
|
|
|
InvalidRequestApiException(int errorCode, String path, this.message)
|
|
|
|
: super(errorCode, path);
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() {
|
|
|
|
return this.message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-17 13:35:57 +00:00
|
|
|
class ApiException implements Exception {
|
|
|
|
final int errorCode;
|
|
|
|
final String path;
|
2021-06-04 09:34:25 +00:00
|
|
|
|
2018-09-17 13:35:57 +00:00
|
|
|
ApiException(this.errorCode, this.path);
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() {
|
|
|
|
return "Can't fetch data from server. (Error-Code: $errorCode)";
|
|
|
|
}
|
2018-09-16 19:47:33 +00:00
|
|
|
}
|