Browse Source

Merge pull request #62 from jld3103/feature/extended-version-checking-support

Implement extended version checking
pull/49/head
jld3103 2 years ago committed by GitHub
parent
commit
348066760a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      packages/dynamite/lib/src/openapi_builder.dart
  2. 9
      packages/neon/lib/l10n/en.arb
  3. 12
      packages/neon/lib/l10n/localizations.dart
  4. 7
      packages/neon/lib/l10n/localizations_en.dart
  5. 2
      packages/neon/lib/src/blocs/apps.dart
  6. 77
      packages/neon/lib/src/pages/home/home.dart
  7. 7
      packages/neon/pubspec.lock
  8. 2
      packages/nextcloud/lib/nextcloud.dart
  9. 5
      packages/nextcloud/lib/src/client.dart
  10. 159
      packages/nextcloud/lib/src/nextcloud.openapi.dart
  11. 8
      packages/nextcloud/lib/src/nextcloud.openapi.g.dart
  12. 28
      packages/nextcloud/lib/src/nextcloud.openapi.json
  13. 32
      packages/nextcloud/lib/src/version_supported.dart
  14. 18
      packages/nextcloud/lib/src/webdav/client.dart
  15. 1
      packages/nextcloud/pubspec.yaml
  16. 6
      packages/nextcloud/test/core_test.dart
  17. 5
      packages/nextcloud/test/news_test.dart
  18. 5
      packages/nextcloud/test/notes_test.dart
  19. 28
      specs/news.json

8
packages/dynamite/lib/src/openapi_builder.dart

@ -732,7 +732,7 @@ class OpenAPIBuilder implements Builder {
],
Method(
(final b) => b
..name = '_doRequest'
..name = 'doRequest'
..returns = refer('Future<Response>')
..modifier = MethodModifier.async
..requiredParameters.addAll([
@ -779,7 +779,7 @@ class OpenAPIBuilder implements Builder {
..fields.add(
Field(
(final b) => b
..name = '_client'
..name = 'rootClient'
..type = refer('Client')
..modifier = FieldModifier.final$,
),
@ -789,7 +789,7 @@ class OpenAPIBuilder implements Builder {
(final b) => b.requiredParameters.add(
Parameter(
(final b) => b
..name = '_client'
..name = 'rootClient'
..toThis = true,
),
),
@ -940,7 +940,7 @@ class OpenAPIBuilder implements Builder {
code.write(
'''
final response = await ${isRootClient ? '' : '_client.'}_doRequest(
final response = await ${isRootClient ? '' : 'rootClient.'}doRequest(
'$httpMethod',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,

9
packages/neon/lib/l10n/en.arb

@ -29,11 +29,11 @@
}
}
},
"errorUnsupportedNextcloudVersion": "Sorry this Nextcloud instance version is not supported. You need at least version {version} of Nextcloud.",
"@errorUnsupportedNextcloudVersion" : {
"errorUnsupportedVersion": "Sorry, this Nextcloud {name} version is not supported.",
"@errorUnsupportedVersion" : {
"placeholders": {
"version": {
"type": "int"
"name": {
"type": "String"
}
}
},
@ -122,6 +122,7 @@
"accountOptionsInitialApp": "App to show initially",
"accountOptionsAutomatic": "Automatic",
"licenses": "Licenses",
"coreName": "Server",
"filesName": "Files",
"filesUploadFiles": "Upload files",
"filesUploadImages": "Upload images",

12
packages/neon/lib/l10n/localizations.dart

@ -179,11 +179,11 @@ abstract class AppLocalizations {
/// **'Permission for {name} is missing'**
String errorMissingPermission(String name);
/// No description provided for @errorUnsupportedNextcloudVersion.
/// No description provided for @errorUnsupportedVersion.
///
/// In en, this message translates to:
/// **'Sorry this Nextcloud instance version is not supported. You need at least version {version} of Nextcloud.'**
String errorUnsupportedNextcloudVersion(int version);
/// **'Sorry, this Nextcloud {name} version is not supported.'**
String errorUnsupportedVersion(String name);
/// No description provided for @errorEmptyField.
///
@ -533,6 +533,12 @@ abstract class AppLocalizations {
/// **'Licenses'**
String get licenses;
/// No description provided for @coreName.
///
/// In en, this message translates to:
/// **'Server'**
String get coreName;
/// No description provided for @filesName.
///
/// In en, this message translates to:

7
packages/neon/lib/l10n/localizations_en.dart

@ -57,8 +57,8 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
String errorUnsupportedNextcloudVersion(int version) {
return 'Sorry this Nextcloud instance version is not supported. You need at least version $version of Nextcloud.';
String errorUnsupportedVersion(String name) {
return 'Sorry, this Nextcloud $name version is not supported.';
}
@override
@ -243,6 +243,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get licenses => 'Licenses';
@override
String get coreName => 'Server';
@override
String get filesName => 'Files';

2
packages/neon/lib/src/blocs/apps.dart

@ -54,7 +54,7 @@ class AppsBloc extends $AppsBloc {
);
} else if (result is ResultCached && result.data != null) {
_appImplementationsSubject.add(
Result.success(_filteredAppImplementations((result as ResultCached<List<NextcloudApp>>).data)),
ResultCached(_filteredAppImplementations((result as ResultCached<List<NextcloudApp>>).data)),
);
}

77
packages/neon/lib/src/pages/home/home.dart

@ -54,26 +54,43 @@ class _HomePageState extends State<HomePage> with tray.TrayListener, WindowListe
// ignore cached version and prevent duplicate dialogs
if (result is ResultSuccess) {
const requiredMajorVersion = 24;
if (result.data!.version!.major! < requiredMajorVersion) {
await showDialog(
context: context,
builder: (final context) => AlertDialog(
title: Text(AppLocalizations.of(context).errorUnsupportedNextcloudVersion(requiredMajorVersion)),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(context).close),
),
],
),
);
}
_appsBloc.appImplementations.listen((final appsResult) async {
// ignore cached version and prevent duplicate dialogs
if (appsResult is ResultSuccess) {
for (final id in [
'core',
...appsResult.data!.map((final a) => a.id),
]) {
try {
bool? supported;
switch (id) {
case 'core':
supported = await widget.account.client.core.isSupported(result.data!);
break;
case 'news':
supported = await widget.account.client.news.isSupported();
break;
case 'notes':
supported = await widget.account.client.notes.isSupported(result.data!);
break;
}
if (!(supported ?? true)) {
if (!mounted) {
return;
}
await _showUnsupportedVersion(
id == 'core'
? AppLocalizations.of(context).coreName
: appsResult.data!.singleWhere((final a) => a.id == id).name(context),
);
}
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
}
}
}
});
}
}
});
@ -259,6 +276,26 @@ class _HomePageState extends State<HomePage> with tray.TrayListener, WindowListe
}
}
Future _showUnsupportedVersion(final String appName) async {
await showDialog(
context: context,
builder: (final context) => AlertDialog(
title: Text(AppLocalizations.of(context).errorUnsupportedVersion(appName)),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(context).close),
),
],
),
);
}
@override
void dispose() {
_capabilitiesBloc.dispose();

7
packages/neon/pubspec.lock

@ -1226,6 +1226,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
version:
dependency: transitive
description:
name: version
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
vm_service:
dependency: transitive
description:

2
packages/nextcloud/lib/nextcloud.dart

@ -7,6 +7,7 @@ import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:version/version.dart';
import 'package:xml/xml.dart' as xml;
export 'package:crypton/crypton.dart' show RSAKeypair, RSAPublicKey, RSAPrivateKey;
@ -16,6 +17,7 @@ export 'src/nextcloud.openapi.dart';
part 'src/app_type.dart';
part 'src/client.dart';
part 'src/helpers.dart';
part 'src/version_supported.dart';
part 'src/webdav/client.dart';
part 'src/webdav/file.dart';
part 'src/webdav/props.dart';

5
packages/nextcloud/lib/src/client.dart

@ -41,9 +41,8 @@ class NextcloudClient extends Client {
}
return _webdav = WebDavClient(
super.baseURL,
basePath: '/remote.php/dav/files/${(super.authentication! as HttpBasicAuthentication).username}',
baseHeaders: super.baseHeaders,
this,
'/remote.php/dav/files/${(super.authentication! as HttpBasicAuthentication).username}',
);
}
}

159
packages/nextcloud/lib/src/nextcloud.openapi.dart

@ -88,7 +88,7 @@ class Client {
NotificationsClient get notifications => NotificationsClient(this);
ProvisioningApiClient get provisioningApi => ProvisioningApiClient(this);
UserStatusClient get userStatus => UserStatusClient(this);
Future<Response> _doRequest(
Future<Response> doRequest(
String method,
String path,
Map<String, String> headers,
@ -1342,16 +1342,16 @@ class CoreLoginFlowResult {
}
class CoreClient {
CoreClient(this._client);
CoreClient(this.rootClient);
final Client _client;
final Client rootClient;
Future<CoreServerStatus> getStatus() async {
var path = '/status.php';
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1368,7 +1368,7 @@ class CoreClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1385,7 +1385,7 @@ class CoreClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1402,7 +1402,7 @@ class CoreClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1420,7 +1420,7 @@ class CoreClient {
final headers = <String, String>{};
Uint8List? body;
queryParameters['token'] = token.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1450,7 +1450,7 @@ class CoreClient {
queryParameters['a'] = a.toString();
queryParameters['forceIcon'] = forceIcon.toString();
queryParameters['mode'] = mode.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1472,7 +1472,7 @@ class CoreClient {
Uint8List? body;
path = path.replaceAll('{userId}', Uri.encodeQueryComponent(userId.toString()));
path = path.replaceAll('{size}', Uri.encodeQueryComponent(size.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1485,6 +1485,17 @@ class CoreClient {
}
}
@JsonSerializable()
class GetSupportedApiVersions {
GetSupportedApiVersions({this.apiLevels});
factory GetSupportedApiVersions.fromJson(Map<String, dynamic> json) => _$GetSupportedApiVersionsFromJson(json);
final List<String>? apiLevels;
Map<String, dynamic> toJson() => _$GetSupportedApiVersionsToJson(this);
}
@JsonSerializable()
class NewsArticle {
NewsArticle({
@ -1669,16 +1680,33 @@ class NewsListArticles {
}
class NewsClient {
NewsClient(this._client);
NewsClient(this.rootClient);
final Client rootClient;
final Client _client;
Future<GetSupportedApiVersions> getSupportedApiVersions() async {
var path = '/apps/news/api';
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
body,
);
if (response.statusCode == 200) {
return GetSupportedApiVersions.fromJson(json.decode(utf8.decode(response.body)) as Map<String, dynamic>);
}
throw ApiException.fromResponse(response);
}
Future<NewsListFolders> listFolders() async {
var path = '/apps/news/api/v1-2/folders';
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1696,7 +1724,7 @@ class NewsClient {
final headers = <String, String>{};
Uint8List? body;
queryParameters['name'] = name.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1718,7 +1746,7 @@ class NewsClient {
Uint8List? body;
path = path.replaceAll('{folderId}', Uri.encodeQueryComponent(folderId.toString()));
queryParameters['name'] = name.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1736,7 +1764,7 @@ class NewsClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{folderId}', Uri.encodeQueryComponent(folderId.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1758,7 +1786,7 @@ class NewsClient {
Uint8List? body;
path = path.replaceAll('{folderId}', Uri.encodeQueryComponent(folderId.toString()));
queryParameters['newestItemId'] = newestItemId.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1775,7 +1803,7 @@ class NewsClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1799,7 +1827,7 @@ class NewsClient {
if (folderId != null) {
queryParameters['folderId'] = folderId.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1817,7 +1845,7 @@ class NewsClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{feedId}', Uri.encodeQueryComponent(feedId.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1841,7 +1869,7 @@ class NewsClient {
if (folderId != null) {
queryParameters['folderId'] = folderId.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1863,7 +1891,7 @@ class NewsClient {
Uint8List? body;
path = path.replaceAll('{feedId}', Uri.encodeQueryComponent(feedId.toString()));
queryParameters['feedTitle'] = feedTitle.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1885,7 +1913,7 @@ class NewsClient {
Uint8List? body;
path = path.replaceAll('{feedId}', Uri.encodeQueryComponent(feedId.toString()));
queryParameters['newestItemId'] = newestItemId.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1915,7 +1943,7 @@ class NewsClient {
queryParameters['batchSize'] = batchSize.toString();
queryParameters['offset'] = offset.toString();
queryParameters['oldestFirst'] = oldestFirst.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1939,7 +1967,7 @@ class NewsClient {
queryParameters['type'] = type.toString();
queryParameters['id'] = id.toString();
queryParameters['lastModified'] = lastModified.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1957,7 +1985,7 @@ class NewsClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{itemId}', Uri.encodeQueryComponent(itemId.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1975,7 +2003,7 @@ class NewsClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{itemId}', Uri.encodeQueryComponent(itemId.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -1997,7 +2025,7 @@ class NewsClient {
Uint8List? body;
path = path.replaceAll('{feedId}', Uri.encodeQueryComponent(feedId.toString()));
path = path.replaceAll('{guidHash}', Uri.encodeQueryComponent(guidHash.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2019,7 +2047,7 @@ class NewsClient {
Uint8List? body;
path = path.replaceAll('{feedId}', Uri.encodeQueryComponent(feedId.toString()));
path = path.replaceAll('{guidHash}', Uri.encodeQueryComponent(guidHash.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2112,9 +2140,9 @@ class NotesSettings {
}
class NotesClient {
NotesClient(this._client);
NotesClient(this.rootClient);
final Client _client;
final Client rootClient;
Future<List<NotesNote>> getNotes({
String? category,
@ -2140,7 +2168,7 @@ class NotesClient {
if (ifNoneMatch != null) {
headers['If-None-Match'] = ifNoneMatch.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2170,7 +2198,7 @@ class NotesClient {
queryParameters['content'] = content.toString();
queryParameters['modified'] = modified.toString();
queryParameters['favorite'] = favorite.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2196,7 +2224,7 @@ class NotesClient {
if (ifNoneMatch != null) {
headers['If-None-Match'] = ifNoneMatch.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2238,7 +2266,7 @@ class NotesClient {
if (ifMatch != null) {
headers['If-Match'] = ifMatch.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2256,7 +2284,7 @@ class NotesClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{id}', Uri.encodeQueryComponent(id.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2273,7 +2301,7 @@ class NotesClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2292,7 +2320,7 @@ class NotesClient {
Uint8List? body;
headers['Content-Type'] = 'application/json';
body = Uint8List.fromList(utf8.encode(json.encode((notesSettings as NotesSettings).toJson())));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2524,16 +2552,16 @@ class NotificationsPushServerRegistration {
}
class NotificationsClient {
NotificationsClient(this._client);
NotificationsClient(this.rootClient);
final Client _client;
final Client rootClient;
Future<NotificationsListNotifications> listNotifications() async {
var path = '/ocs/v1.php/apps/notifications/api/v2/notifications';
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2550,7 +2578,7 @@ class NotificationsClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2568,7 +2596,7 @@ class NotificationsClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{id}', Uri.encodeQueryComponent(id.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2586,7 +2614,7 @@ class NotificationsClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{id}', Uri.encodeQueryComponent(id.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2610,7 +2638,7 @@ class NotificationsClient {
queryParameters['pushTokenHash'] = pushTokenHash.toString();
queryParameters['devicePublicKey'] = devicePublicKey.toString();
queryParameters['proxyServer'] = proxyServer.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2628,7 +2656,7 @@ class NotificationsClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2652,7 +2680,7 @@ class NotificationsClient {
path = path.replaceAll('{userId}', Uri.encodeQueryComponent(userId.toString()));
queryParameters['shortMessage'] = shortMessage.toString();
queryParameters['longMessage'] = longMessage.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'post',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2866,16 +2894,16 @@ class ProvisioningApiUser {
}
class ProvisioningApiClient {
ProvisioningApiClient(this._client);
ProvisioningApiClient(this.rootClient);
final Client _client;
final Client rootClient;
Future<ProvisioningApiUser> getCurrentUser() async {
var path = '/ocs/v1.php/cloud/user';
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -2893,7 +2921,7 @@ class ProvisioningApiClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{userId}', Uri.encodeQueryComponent(userId.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3321,16 +3349,16 @@ class UserStatusPredefinedStatuses {
}
class UserStatusClient {
UserStatusClient(this._client);
UserStatusClient(this.rootClient);
final Client _client;
final Client rootClient;
Future<UserStatusFindAllStatuses> findAllStatuses() async {
var path = '/ocs/v1.php/apps/user_status/api/v1/statuses';
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3348,7 +3376,7 @@ class UserStatusClient {
final headers = <String, String>{};
Uint8List? body;
path = path.replaceAll('{userId}', Uri.encodeQueryComponent(userId.toString()));
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3365,7 +3393,7 @@ class UserStatusClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3383,7 +3411,7 @@ class UserStatusClient {
final headers = <String, String>{};
Uint8List? body;
queryParameters['statusType'] = statusType.value.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3407,7 +3435,7 @@ class UserStatusClient {
if (clearAt != null) {
queryParameters['clearAt'] = clearAt.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3437,7 +3465,7 @@ class UserStatusClient {
if (clearAt != null) {
queryParameters['clearAt'] = clearAt.toString();
}
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3454,7 +3482,7 @@ class UserStatusClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'delete',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3471,7 +3499,7 @@ class UserStatusClient {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{};
Uint8List? body;
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'get',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3489,7 +3517,7 @@ class UserStatusClient {
final headers = <String, String>{};
Uint8List? body;
queryParameters['status'] = status.value.toString();
final response = await _client._doRequest(
final response = await rootClient.doRequest(
'put',
Uri(path: path, queryParameters: queryParameters).toString(),
headers,
@ -3894,6 +3922,10 @@ final _deserializers = <Type, dynamic Function(dynamic)>{
List<CoreLoginFlowResult>: (final data) => (data as List)
.map<CoreLoginFlowResult>((final e) => CoreLoginFlowResult.fromJson(e as Map<String, dynamic>))
.toList(),
GetSupportedApiVersions: (final data) => GetSupportedApiVersions.fromJson(data as Map<String, dynamic>),
List<GetSupportedApiVersions>: (final data) => (data as List)
.map<GetSupportedApiVersions>((final e) => GetSupportedApiVersions.fromJson(e as Map<String, dynamic>))
.toList(),
NewsListFolders: (final data) => NewsListFolders.fromJson(data as Map<String, dynamic>),
List<NewsListFolders>: (final data) =>
(data as List).map<NewsListFolders>((final e) => NewsListFolders.fromJson(e as Map<String, dynamic>)).toList(),
@ -4376,6 +4408,9 @@ final _serializers = <Type, dynamic Function(dynamic)>{
CoreLoginFlowResult: (final data) => (data as CoreLoginFlowResult).toJson(),
List<CoreLoginFlowResult>: (final data) =>
(data as List<CoreLoginFlowResult>).map((final e) => (e as CoreLoginFlowResult).toJson()).toList(),
GetSupportedApiVersions: (final data) => (data as GetSupportedApiVersions).toJson(),
List<GetSupportedApiVersions>: (final data) =>
(data as List<GetSupportedApiVersions>).map((final e) => (e as GetSupportedApiVersions).toJson()).toList(),
NewsListFolders: (final data) => (data as NewsListFolders).toJson(),
List<NewsListFolders>: (final data) =>
(data as List<NewsListFolders>).map((final e) => (e as NewsListFolders).toJson()).toList(),

8
packages/nextcloud/lib/src/nextcloud.openapi.g.dart

@ -1036,6 +1036,14 @@ Map<String, dynamic> _$CoreLoginFlowResultToJson(CoreLoginFlowResult instance) =
'appPassword': instance.appPassword,
};
GetSupportedApiVersions _$GetSupportedApiVersionsFromJson(Map<String, dynamic> json) => GetSupportedApiVersions(
apiLevels: (json['apiLevels'] as List<dynamic>?)?.map((e) => e as String).toList(),
);
Map<String, dynamic> _$GetSupportedApiVersionsToJson(GetSupportedApiVersions instance) => <String, dynamic>{
'apiLevels': instance.apiLevels,
};
NewsArticle _$NewsArticleFromJson(Map<String, dynamic> json) => NewsArticle(
id: json['id'] as int?,
guid: json['guid'] as String?,

28
packages/nextcloud/lib/src/nextcloud.openapi.json

@ -1819,6 +1819,34 @@
}
}
},
"/apps/news/api": {
"get": {
"operationId": "get-supported-api-versions",
"tags": [
"news"
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"apiLevels": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}
},
"/apps/news/api/v1-2/folders": {
"get": {
"operationId": "list-folders",

32
packages/nextcloud/lib/src/version_supported.dart

@ -0,0 +1,32 @@
part of '../nextcloud.dart';
// ignore: public_member_api_docs
extension CoreVersionSupported on CoreClient {
/// Checks if the app on the server is supported by the client
Future<bool> isSupported([final CoreServerCapabilitiesOcsData? capabilities]) async =>
(capabilities ?? (await rootClient.core.getCapabilities()).ocs!.data!).version!.major! == 24;
}
// ignore: public_member_api_docs
extension NewsVersionSupported on NewsClient {
/// Checks if the app on the server is supported by the client
Future<bool> isSupported() async {
final versions = await getSupportedApiVersions();
return versions.apiLevels!.contains('v1-2');
}
}
// ignore: public_member_api_docs
extension NotesVersionSupported on NotesClient {
/// Checks if the app on the server is supported by the client
Future<bool> isSupported([final CoreServerCapabilitiesOcsData? capabilities]) async =>
(capabilities ?? (await rootClient.core.getCapabilities()).ocs!.data!)
.capabilities!
.notes!
.apiVersion!
.map(Version.parse)
.where((final version) => version.major == 1)
.isNotEmpty;
}
// Notifications, ProvisioningApi, UserStatus and Webdav are shipped with the Nextcloud server, so their supported versions depend on the major version of the Nextcloud instance.

18
packages/nextcloud/lib/src/webdav/client.dart

@ -4,20 +4,16 @@ part of '../../nextcloud.dart';
class WebDavClient {
// ignore: public_member_api_docs
WebDavClient(
this.baseUrl, {
this.basePath = '',
this.baseHeaders,
});
this.rootClient,
this.basePath,
);
/// Base URL of the server
final String baseUrl;
// ignore: public_member_api_docs
final Client rootClient;
/// Base path used on the server
final String basePath;
/// Headers added to each request. Useful for authentication
final Map<String, String>? baseHeaders;
/// XML namespaces supported by this client.
///
/// For Nextcloud namespaces see [WebDav/Requesting properties](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html#requesting-properties).
@ -45,7 +41,7 @@ class WebDavClient {
..persistentConnection = true;
for (final header in {
HttpHeaders.contentTypeHeader: 'application/xml',
if (baseHeaders != null) ...baseHeaders!,
...rootClient.baseHeaders,
if (headers != null) ...headers,
}.entries) {
request.headers.add(header.key, header.value);
@ -75,7 +71,7 @@ class WebDavClient {
namespaces.putIfAbsent(namespaceUri, () => prefix);
String _constructPath([final String? path]) => [
baseUrl,
rootClient.baseURL,
basePath,
if (path != null) ...[
path,

1
packages/nextcloud/pubspec.yaml

@ -11,6 +11,7 @@ dependencies:
intl: ^0.17.0
json_annotation: ^4.6.0
meta: ^1.7.0
version: ^3.0.2
xml: ^6.1.0
dev_dependencies:

6
packages/nextcloud/test/core_test.dart

@ -1,3 +1,4 @@
import 'package:nextcloud/nextcloud.dart';
import 'package:test/test.dart';
import 'helper.dart';
@ -17,6 +18,11 @@ Future main() async {
});
tearDown(() => client.destroy());
test('Is supported', () async {
final response = await client.core.isSupported();
expect(response, isTrue);
});
test('Get status', () async {
final status = await client.core.getStatus();
expect(status.installed, true);

5
packages/nextcloud/test/news_test.dart

@ -25,6 +25,11 @@ Future main() async {
url: nasaFeedURL,
);
test('Is supported', () async {
final response = await client.news.isSupported();
expect(response, isTrue);
});
test('Add feed', () async {
var response = await client.news.listFeeds();
expect(response.starredCount, 0);

5
packages/nextcloud/test/notes_test.dart

@ -13,6 +13,11 @@ Future main() async {
});
tearDown(() => client.destroy());
test('Is supported', () async {
final response = await client.notes.isSupported();
expect(response, isTrue);
});
test('Create note favorite', () async {
final response = await client.notes.createNote(
title: 'a',

28
specs/news.json

@ -236,6 +236,34 @@
}
},
"paths": {
"/apps/news/api": {
"get": {
"operationId": "get-supported-api-versions",
"tags": [
"news"
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"apiLevels": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}
},
"/apps/news/api/v1-2/folders": {
"get": {
"operationId": "list-folders",

Loading…
Cancel
Save