Browse Source

Merge pull request #56 from jld3103/update/flutter

Update flutter
pull/57/head
jld3103 2 years ago committed by GitHub
parent
commit
5e743e8adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .fvm/fvm_config.json
  2. 4
      packages/file_icons/analysis_options.yaml
  3. 4
      packages/file_icons/pubspec.yaml
  4. 4
      packages/neon/analysis_options.yaml
  5. 26
      packages/neon/integration_test/screenshot_test.dart
  6. 4
      packages/neon/lib/app.dart
  7. 14
      packages/neon/lib/l10n/localizations.dart
  8. 10
      packages/neon/lib/src/apps/files/app.dart
  9. 6
      packages/neon/lib/src/apps/files/blocs/browser.dart
  10. 6
      packages/neon/lib/src/apps/files/blocs/files.dart
  11. 2
      packages/neon/lib/src/apps/files/dialogs/choose_folder.dart
  12. 2
      packages/neon/lib/src/apps/files/pages/details.dart
  13. 2
      packages/neon/lib/src/apps/files/pages/main.dart
  14. 2
      packages/neon/lib/src/apps/files/widgets/file_preview.dart
  15. 10
      packages/neon/lib/src/apps/news/app.dart
  16. 8
      packages/neon/lib/src/apps/news/blocs/articles.dart
  17. 8
      packages/neon/lib/src/apps/news/blocs/news.dart
  18. 4
      packages/neon/lib/src/apps/news/dialogs/add_feed.dart
  19. 4
      packages/neon/lib/src/apps/news/pages/article.dart
  20. 10
      packages/neon/lib/src/apps/notes/app.dart
  21. 4
      packages/neon/lib/src/apps/notes/blocs/notes.dart
  22. 8
      packages/neon/lib/src/apps/notes/pages/note.dart
  23. 10
      packages/neon/lib/src/apps/notifications/app.dart
  24. 6
      packages/neon/lib/src/apps/notifications/blocs/notifications.dart
  25. 5
      packages/neon/lib/src/apps/notifications/pages/main.dart
  26. 9
      packages/neon/lib/src/blocs/accounts.dart
  27. 10
      packages/neon/lib/src/blocs/apps.dart
  28. 4
      packages/neon/lib/src/blocs/capabilities.dart
  29. 8
      packages/neon/lib/src/blocs/login.dart
  30. 6
      packages/neon/lib/src/blocs/push_notifications.dart
  31. 4
      packages/neon/lib/src/blocs/user_details.dart
  32. 4
      packages/neon/lib/src/blocs/user_status.dart
  33. 7
      packages/neon/lib/src/models/account.dart
  34. 2
      packages/neon/lib/src/models/nextcloud_notification.dart
  35. 1
      packages/neon/lib/src/neon.dart
  36. 10
      packages/neon/lib/src/pages/home/home.dart
  37. 4
      packages/neon/lib/src/pages/settings/settings.dart
  38. 14
      packages/neon/lib/src/utils/app_implementation.dart
  39. 4
      packages/neon/lib/src/utils/confirmation_dialog.dart
  40. 4
      packages/neon/lib/src/utils/global_options.dart
  41. 11
      packages/neon/lib/src/utils/push_utils.dart
  42. 4
      packages/neon/lib/src/utils/theme.dart
  43. 2
      packages/neon/lib/src/widgets/image_wrapper.dart
  44. 78
      packages/neon/pubspec.lock
  45. 11
      packages/neon/pubspec.yaml
  46. 4
      packages/nextcloud/analysis_options.yaml
  47. 2
      packages/nextcloud/lib/src/clients/notifications.dart
  48. 2
      packages/nextcloud/pubspec.yaml
  49. 4
      packages/nextcloud_push_proxy/analysis_options.yaml
  50. 6
      packages/nextcloud_push_proxy/lib/nextcloud_push_proxy.dart
  51. 2
      packages/nextcloud_push_proxy/pubspec.yaml
  52. 4
      packages/settings/analysis_options.yaml
  53. 2
      packages/settings/lib/settings.dart
  54. 4
      packages/settings/lib/src/options/option.dart
  55. 4
      packages/settings/lib/src/options/select_option.dart
  56. 4
      packages/settings/pubspec.yaml
  57. 2
      packages/sort_box/pubspec.yaml
  58. 4
      packages/spec_templates/analysis_options.yaml
  59. 2
      packages/spec_templates/pubspec.yaml

2
.fvm/fvm_config.json

@ -1,4 +1,4 @@
{
"flutterSdkVersion": "3.0.5@stable",
"flutterSdkVersion": "3.3.0@stable",
"flavors": {}
}

4
packages/file_icons/analysis_options.yaml

@ -1,5 +1 @@
include: package:nit_picking/dart.yaml
linter:
rules:
prefer_final_parameters: false # Disabled until super.X is no longer complained about in constructors

4
packages/file_icons/pubspec.yaml

@ -2,8 +2,8 @@ name: file_icons
version: 1.0.0
environment:
sdk: '>=2.17.0 <3.0.0'
flutter: '>=3.0.0'
sdk: '>=2.18.0 <3.0.0'
flutter: '>=3.3.0'
dependencies:
flutter:

4
packages/neon/analysis_options.yaml

@ -1,9 +1,5 @@
include: package:nit_picking/flutter.yaml
linter:
rules:
prefer_final_parameters: false # Disabled until super.X is no longer complained about in constructors
analyzer:
exclude:
- lib/src/l10n/**

26
packages/neon/integration_test/screenshot_test.dart

@ -34,7 +34,7 @@ class MemorySharedPreferences implements SharedPreferences {
Future reload() async {}
@override
Future<bool> remove(String key) async {
Future<bool> remove(final String key) async {
_data.remove(key);
return true;
}
@ -43,52 +43,52 @@ class MemorySharedPreferences implements SharedPreferences {
Set<String> getKeys() => _data.keys.toSet();
@override
bool containsKey(String key) => _data.keys.contains(key);
bool containsKey(final String key) => _data.keys.contains(key);
@override
Object? get(String key) => _data[key];
Object? get(final String key) => _data[key];
@override
bool? getBool(String key) => _data[key] as bool?;
bool? getBool(final String key) => _data[key] as bool?;
@override
double? getDouble(String key) => _data[key] as double?;
double? getDouble(final String key) => _data[key] as double?;
@override
int? getInt(String key) => _data[key] as int?;
int? getInt(final String key) => _data[key] as int?;
@override
String? getString(String key) => _data[key] as String?;
String? getString(final String key) => _data[key] as String?;
@override
List<String>? getStringList(String key) => (_data[key] as List).cast<String>();
List<String>? getStringList(final String key) => (_data[key] as List).cast<String>();
@override
Future<bool> setBool(String key, bool value) async {
Future<bool> setBool(final String key, final bool value) async {
_data[key] = value;
return true;
}
@override
Future<bool> setDouble(String key, double value) async {
Future<bool> setDouble(final String key, final double value) async {
_data[key] = value;
return true;
}
@override
Future<bool> setInt(String key, int value) async {
Future<bool> setInt(final String key, final int value) async {
_data[key] = value;
return true;
}
@override
Future<bool> setString(String key, String value) async {
Future<bool> setString(final String key, final String value) async {
_data[key] = value;
return true;
}
@override
Future<bool> setStringList(String key, List<String> value) async {
Future<bool> setStringList(final String key, final List<String> value) async {
_data[key] = value;
return true;
}

4
packages/neon/lib/app.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:neon/l10n/localizations.dart';
@ -84,7 +86,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver {
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_platformBrightness.close();
unawaited(_platformBrightness.close());
super.dispose();
}

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

@ -7,14 +7,14 @@ import 'package:intl/intl.dart' as intl;
import 'localizations_en.dart';
/// Callers can lookup localized strings with an instance of AppLocalizations returned
/// by `AppLocalizations.of(context)`.
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// localizationDelegates list, and the locales they support in the app's
/// supportedLocales list. For example:
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```
/// ```dart
/// import 'l10n/localizations.dart';
///
/// return MaterialApp(
@ -29,14 +29,14 @@ import 'localizations_en.dart';
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # rest of dependencies
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications

10
packages/neon/lib/src/apps/files/app.dart

@ -46,13 +46,13 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
String id = 'files';
@override
String nameFromLocalization(AppLocalizations localizations) => localizations.filesName;
String nameFromLocalization(final AppLocalizations localizations) => localizations.filesName;
@override
FilesAppSpecificOptions buildOptions(Storage storage) => FilesAppSpecificOptions(storage);
FilesAppSpecificOptions buildOptions(final Storage storage) => FilesAppSpecificOptions(storage);
@override
FilesBloc buildBloc(NextcloudClient client) => FilesBloc(
FilesBloc buildBloc(final NextcloudClient client) => FilesBloc(
options,
requestManager,
client,
@ -60,10 +60,10 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
);
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => FilesMainPage(
Widget buildPage(final BuildContext context, final AppsBloc appsBloc) => FilesMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) => null;
BehaviorSubject<int>? getUnreadCounter(final AppsBloc appsBloc) => null;
}

6
packages/neon/lib/src/apps/files/blocs/browser.dart

@ -86,9 +86,9 @@ class FilesBrowserBloc extends $FilesBrowserBloc {
@override
void dispose() {
_filesSubject.close();
_pathSubject.close();
_errorsStreamController.close();
unawaited(_filesSubject.close());
unawaited(_pathSubject.close());
unawaited(_errorsStreamController.close());
super.dispose();
}

6
packages/neon/lib/src/apps/files/blocs/files.dart

@ -226,9 +226,9 @@ class FilesBloc extends $FilesBloc {
void dispose() {
_uploadQueue.dispose();
_downloadQueue.dispose();
_uploadTasksSubject.close();
_downloadTasksSubject.close();
_errorsStreamController.close();
unawaited(_uploadTasksSubject.close());
unawaited(_downloadTasksSubject.close());
unawaited(_errorsStreamController.close());
super.dispose();
}

2
packages/neon/lib/src/apps/files/dialogs/choose_folder.dart

@ -14,7 +14,7 @@ class FilesChooseFolderDialog extends StatelessWidget {
final List<String> originalPath;
@override
Widget build(BuildContext context) => AlertDialog(
Widget build(final BuildContext context) => AlertDialog(
title: Text(AppLocalizations.of(context).filesChooseFolder),
contentPadding: EdgeInsets.zero,
content: SizedBox(

2
packages/neon/lib/src/apps/files/pages/details.dart

@ -11,7 +11,7 @@ class FilesDetailsPage extends StatelessWidget {
final FileDetails details;
@override
Widget build(BuildContext context) => Scaffold(
Widget build(final BuildContext context) => Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(details.name),

2
packages/neon/lib/src/apps/files/pages/main.dart

@ -23,7 +23,7 @@ class _FilesMainPageState extends State<FilesMainPage> {
}
@override
Widget build(BuildContext context) => FilesBrowserView(
Widget build(final BuildContext context) => FilesBrowserView(
bloc: widget.bloc.browser,
filesBloc: widget.bloc,
onPickFile: (final details) async {

2
packages/neon/lib/src/apps/files/widgets/file_preview.dart

@ -24,7 +24,7 @@ class FilePreview extends StatelessWidget {
final bool withBackground;
@override
Widget build(BuildContext context) {
Widget build(final BuildContext context) {
final color = this.color ?? Theme.of(context).colorScheme.primary;
return SizedBox(
width: width.toDouble(),

10
packages/neon/lib/src/apps/news/app.dart

@ -54,23 +54,23 @@ class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
String id = 'news';
@override
String nameFromLocalization(AppLocalizations localizations) => localizations.newsName;
String nameFromLocalization(final AppLocalizations localizations) => localizations.newsName;
@override
NewsAppSpecificOptions buildOptions(Storage storage) => NewsAppSpecificOptions(storage, platform);
NewsAppSpecificOptions buildOptions(final Storage storage) => NewsAppSpecificOptions(storage, platform);
@override
NewsBloc buildBloc(NextcloudClient client) => NewsBloc(
NewsBloc buildBloc(final NextcloudClient client) => NewsBloc(
options,
requestManager,
client,
);
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => NewsMainPage(
Widget buildPage(final BuildContext context, final AppsBloc appsBloc) => NewsMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) => appsBloc.getAppBloc<NewsBloc>(this).unreadCounter;
BehaviorSubject<int>? getUnreadCounter(final AppsBloc appsBloc) => appsBloc.getAppBloc<NewsBloc>(this).unreadCounter;
}

8
packages/neon/lib/src/apps/news/blocs/articles.dart

@ -182,10 +182,10 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
@override
void dispose() {
_articlesSubject.close();
_filterTypeSubject.close();
_articleUpdateController.close();
_errorsStreamController.close();
unawaited(_articlesSubject.close());
unawaited(_filterTypeSubject.close());
unawaited(_articleUpdateController.close());
unawaited(_errorsStreamController.close());
super.dispose();
}

8
packages/neon/lib/src/apps/news/blocs/news.dart

@ -237,10 +237,10 @@ class NewsBloc extends $NewsBloc {
@override
void dispose() {
_foldersSubject.close();
_feedsSubject.close();
_errorsStreamController.close();
_unreadCounterSubject.close();
unawaited(_foldersSubject.close());
unawaited(_feedsSubject.close());
unawaited(_errorsStreamController.close());
unawaited(_unreadCounterSubject.close());
super.dispose();
}

4
packages/neon/lib/src/apps/news/dialogs/add_feed.dart

@ -30,6 +30,7 @@ class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> {
void initState() {
super.initState();
unawaited(
Clipboard.getData(Clipboard.kTextPlain).then((final clipboardContent) {
if (clipboardContent != null && clipboardContent.text != null) {
final uri = Uri.tryParse(clipboardContent.text!);
@ -37,7 +38,8 @@ class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> {
controller.text = clipboardContent.text!;
}
}
});
}),
);
}
@override

4
packages/neon/lib/src/apps/news/pages/article.dart

@ -37,9 +37,9 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
}
});
WidgetsBinding.instance.addPostFrameCallback((final _) {
WidgetsBinding.instance.addPostFrameCallback((final _) async {
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
Wakelock.enable();
await Wakelock.enable();
}
});

10
packages/neon/lib/src/apps/notes/app.dart

@ -41,23 +41,23 @@ class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
String id = 'notes';
@override
String nameFromLocalization(AppLocalizations localizations) => localizations.notesName;
String nameFromLocalization(final AppLocalizations localizations) => localizations.notesName;
@override
NotesAppSpecificOptions buildOptions(Storage storage) => NotesAppSpecificOptions(storage);
NotesAppSpecificOptions buildOptions(final Storage storage) => NotesAppSpecificOptions(storage);
@override
NotesBloc buildBloc(NextcloudClient client) => NotesBloc(
NotesBloc buildBloc(final NextcloudClient client) => NotesBloc(
options,
requestManager,
client,
);
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => NotesMainPage(
Widget buildPage(final BuildContext context, final AppsBloc appsBloc) => NotesMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) => null;
BehaviorSubject<int>? getUnreadCounter(final AppsBloc appsBloc) => null;
}

4
packages/neon/lib/src/apps/notes/blocs/notes.dart

@ -90,8 +90,8 @@ class NotesBloc extends $NotesBloc {
@override
void dispose() {
_notesSubject.close();
_errorsStreamController.close();
unawaited(_notesSubject.close());
unawaited(_errorsStreamController.close());
super.dispose();
}

8
packages/neon/lib/src/apps/notes/pages/note.dart

@ -72,9 +72,9 @@ class _NotesNotePageState extends State<NotesNotePage> {
}
});
WidgetsBinding.instance.addPostFrameCallback((final _) {
WidgetsBinding.instance.addPostFrameCallback((final _) async {
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
Wakelock.enable();
await Wakelock.enable();
}
if (widget.bloc.options.defaultNoteViewTypeOption.value == DefaultNoteViewType.edit ||
widget.note.content!.isEmpty) {
@ -189,9 +189,9 @@ class _NotesNotePageState extends State<NotesNotePage> {
)
: MarkdownBody(
data: _contentController.text,
onTapLink: (final text, final href, final title) {
onTapLink: (final text, final href, final title) async {
if (href != null) {
launchUrlString(
await launchUrlString(
href,
mode: LaunchMode.externalApplication,
);

10
packages/neon/lib/src/apps/notifications/app.dart

@ -21,24 +21,24 @@ class NotificationsApp extends AppImplementation<NotificationsBloc, Notification
String id = 'notifications';
@override
String nameFromLocalization(AppLocalizations localizations) => localizations.notificationsName;
String nameFromLocalization(final AppLocalizations localizations) => localizations.notificationsName;
@override
NotificationsAppSpecificOptions buildOptions(Storage storage) => NotificationsAppSpecificOptions(storage);
NotificationsAppSpecificOptions buildOptions(final Storage storage) => NotificationsAppSpecificOptions(storage);
@override
NotificationsBloc buildBloc(NextcloudClient client) => NotificationsBloc(
NotificationsBloc buildBloc(final NextcloudClient client) => NotificationsBloc(
options,
requestManager,
client,
);
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => NotificationsMainPage(
Widget buildPage(final BuildContext context, final AppsBloc appsBloc) => NotificationsMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) =>
BehaviorSubject<int>? getUnreadCounter(final AppsBloc appsBloc) =>
appsBloc.getAppBloc<NotificationsBloc>(this).unreadCounter;
}

6
packages/neon/lib/src/apps/notifications/blocs/notifications.dart

@ -83,9 +83,9 @@ class NotificationsBloc extends $NotificationsBloc {
@override
void dispose() {
_notificationsSubject.close();
_errorsStreamController.close();
_unreadCounterSubject.close();
unawaited(_notificationsSubject.close());
unawaited(_errorsStreamController.close());
unawaited(_unreadCounterSubject.close());
super.dispose();
}

5
packages/neon/lib/src/apps/notifications/pages/main.dart

@ -23,7 +23,8 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
}
@override
Widget build(BuildContext context) => StandardRxResultBuilder<NotificationsBloc, List<NotificationsNotification>>(
Widget build(final BuildContext context) =>
StandardRxResultBuilder<NotificationsBloc, List<NotificationsNotification>>(
bloc: widget.bloc,
state: (final bloc) => bloc.notifications,
builder: (
@ -130,7 +131,7 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.red,
backgroundColor: Colors.red,
),
onPressed: () {
Navigator.of(context).pop();

9
packages/neon/lib/src/blocs/accounts.dart

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:neon/src/blocs/user_details.dart';
@ -106,12 +107,14 @@ class AccountsBloc extends $AccountsBloc {
final lastUsedAccountID = _storage.getString(_keyLastUsedAccount);
_activeAccountSubject.add(accounts.singleWhere((final account) => account.id == lastUsedAccountID));
} else {
unawaited(
_globalOptions.initialAccount.stream.first.then((final lastAccount) {
final matches = accounts.where((final account) => account.id == lastAccount).toList();
if (matches.isNotEmpty) {
_activeAccountSubject.add(matches[0]);
}
});
}),
);
}
}
@ -170,8 +173,8 @@ class AccountsBloc extends $AccountsBloc {
@override
void dispose() {
_activeAccountSubject.close();
_accountsSubject.close();
unawaited(_activeAccountSubject.close());
unawaited(_accountsSubject.close());
for (final bloc in _userDetailsBlocs.values) {
bloc.dispose();
}

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

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart';
@ -65,6 +67,7 @@ class AppsBloc extends $AppsBloc {
_accountsBloc.pushNotificationApp = null;
} else {
final options = _accountsBloc.getOptions(_account)!..updateApps(appImplementations);
unawaited(
options.initialApp.stream.first.then((var initialApp) {
if (initialApp == null) {
if (appImplementations.where((final a) => a.id == 'files').isNotEmpty) {
@ -78,7 +81,8 @@ class AppsBloc extends $AppsBloc {
if (!_activeAppSubject.hasValue) {
setActiveApp(initialApp);
}
});
}),
);
}
}
});
@ -130,8 +134,8 @@ class AppsBloc extends $AppsBloc {
@override
void dispose() {
_appsSubject.close();
_activeAppSubject.close();
unawaited(_appsSubject.close());
unawaited(_activeAppSubject.close());
for (final key in _blocs.keys) {
_blocs[key]!.dispose();
}

4
packages/neon/lib/src/blocs/capabilities.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart';
@ -48,7 +50,7 @@ class CapabilitiesBloc extends $CapabilitiesBloc {
@override
void dispose() {
_capabilitiesSubject.close();
unawaited(_capabilitiesSubject.close());
super.dispose();
}

8
packages/neon/lib/src/blocs/login.dart

@ -86,10 +86,10 @@ class LoginBloc extends $LoginBloc {
@override
void dispose() {
_cancelPollTimer();
_serverURLSubject.close();
_serverConnectionStateSubject.close();
_loginFlowInitSubject.close();
_loginFlowResultSubject.close();
unawaited(_serverURLSubject.close());
unawaited(_serverConnectionStateSubject.close());
unawaited(_loginFlowInitSubject.close());
unawaited(_loginFlowResultSubject.close());
super.dispose();
}

6
packages/neon/lib/src/blocs/push_notifications.dart

@ -28,14 +28,14 @@ class PushNotificationsBloc extends $PushNotificationsBloc {
this._platform,
) {
if (_platform.canUsePushNotifications) {
UnifiedPush.getDistributors().then(_globalOptions.updateDistributors);
unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors));
_globalOptions.pushNotificationsEnabled.stream.listen((final enabled) async {
if (enabled != _pushNotificationsEnabled) {
_pushNotificationsEnabled = enabled;
if (enabled) {
// We just use a single RSA keypair for all accounts
_keypair = PushUtils.loadRSAKeypair(_storage);
_keypair = await PushUtils.loadRSAKeypair(_storage);
await _setupUnifiedPush();
}
}
@ -138,7 +138,7 @@ class PushNotificationsBloc extends $PushNotificationsBloc {
@override
void dispose() {
_notificationsController.close();
unawaited(_notificationsController.close());
super.dispose();
}

4
packages/neon/lib/src/blocs/user_details.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart';
@ -45,7 +47,7 @@ class UserDetailsBloc extends $UserDetailsBloc {
@override
void dispose() {
_userDetailsSubject.close();
unawaited(_userDetailsSubject.close());
super.dispose();
}

4
packages/neon/lib/src/blocs/user_status.dart

@ -80,8 +80,8 @@ class UserStatusBloc extends $UserStatusBloc {
@override
void dispose() {
_cancelTimer();
_activeAccountStreamSubscription.cancel();
_userStatusSubject.close();
unawaited(_activeAccountStreamSubscription.cancel());
unawaited(_userStatusSubject.close());
super.dispose();
}

7
packages/neon/lib/src/models/account.dart

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:crypto/crypto.dart';
@ -11,7 +12,7 @@ import 'package:settings/settings.dart';
part 'account.g.dart';
String userAgent(PackageInfo packageInfo) => 'Neon ${packageInfo.version}+${packageInfo.buildNumber}';
String userAgent(final PackageInfo packageInfo) => 'Neon ${packageInfo.version}+${packageInfo.buildNumber}';
@JsonSerializable()
class Account {
@ -50,7 +51,7 @@ class Account {
NextcloudClient? _client;
void setupClient(PackageInfo packageInfo) {
void setupClient(final PackageInfo packageInfo) {
_client ??= NextcloudClient(
serverURL,
username: username,
@ -115,7 +116,7 @@ class AccountSpecificOptions {
}
void dispose() {
_appIDsSubject.close();
unawaited(_appIDsSubject.close());
for (final option in options) {
option.dispose();
}

2
packages/neon/lib/src/models/nextcloud_notification.dart

@ -29,4 +29,4 @@ class NextcloudNotification {
NotificationsPushNotificationDecryptedSubject _fromJsonSubject(final Map<String, dynamic> data) =>
NotificationsPushNotificationDecryptedSubject.fromJson(data)!;
Map<String, dynamic>? _toJsonSubject(NotificationsPushNotificationDecryptedSubject subject) => subject.toJson();
Map<String, dynamic>? _toJsonSubject(final NotificationsPushNotificationDecryptedSubject subject) => subject.toJson();

1
packages/neon/lib/src/neon.dart

@ -4,7 +4,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';

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

@ -48,7 +48,7 @@ class _HomePageState extends State<HomePage> with tray.TrayListener, WindowListe
_requestManager,
widget.account.client,
);
_capabilitiesBloc.capabilities.listen((final result) {
_capabilitiesBloc.capabilities.listen((final result) async {
if (result.data != null) {
widget.onThemeChanged(result.data!.capabilities!.theming!);
@ -56,14 +56,14 @@ class _HomePageState extends State<HomePage> with tray.TrayListener, WindowListe
if (result is ResultSuccess) {
const requiredMajorVersion = 24;
if (result.data!.version!.major! < requiredMajorVersion) {
showDialog(
await showDialog(
context: context,
builder: (final context) => AlertDialog(
title: Text(AppLocalizations.of(context).errorUnsupportedNextcloudVersion(requiredMajorVersion)),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.red,
backgroundColor: Colors.red,
),
onPressed: () {
Navigator.of(context).pop();
@ -200,9 +200,9 @@ class _HomePageState extends State<HomePage> with tray.TrayListener, WindowListe
}
@override
void onTrayMenuItemClick(tray.MenuItem menuItem) {
void onTrayMenuItemClick(final tray.MenuItem menuItem) {
if (menuItem.key != null) {
_handleShortcut(menuItem.key!);
unawaited(_handleShortcut(menuItem.key!));
}
}

4
packages/neon/lib/src/pages/settings/settings.dart

@ -179,8 +179,8 @@ class _SettingsPageState extends State<SettingsPage> {
for (final account in accountsSnapshot.data!) ...[
AccountSettingsTile(
account: account,
onTap: () {
Navigator.of(context).push(
onTap: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => AccountSpecificSettingsPage(
bloc: accountsBloc,

14
packages/neon/lib/src/utils/app_implementation.dart

@ -15,8 +15,8 @@ List<AppImplementation> getAppImplementations(
abstract class AppImplementation<T extends RxBlocBase, R extends NextcloudAppSpecificOptions> {
AppImplementation(
final SharedPreferences sharedPreferences,
final this.requestManager,
final this.platform,
this.requestManager,
this.platform,
) {
final storage = Storage('app-$id', sharedPreferences);
options = buildOptions(storage);
@ -26,17 +26,17 @@ abstract class AppImplementation<T extends RxBlocBase, R extends NextcloudAppSpe
final RequestManager requestManager;
final NeonPlatform platform;
String nameFromLocalization(AppLocalizations localizations);
String name(BuildContext context) => nameFromLocalization(AppLocalizations.of(context));
String nameFromLocalization(final AppLocalizations localizations);
String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context));
late final R options;
R buildOptions(Storage storage);
R buildOptions(final Storage storage);
T buildBloc(final NextcloudClient client);
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc);
BehaviorSubject<int>? getUnreadCounter(final AppsBloc appsBloc);
Widget buildPage(BuildContext context, AppsBloc appsBloc);
Widget buildPage(final BuildContext context, final AppsBloc appsBloc);
Widget buildIcon(
final BuildContext context, {

4
packages/neon/lib/src/utils/confirmation_dialog.dart

@ -9,7 +9,7 @@ Future<bool> showConfirmationDialog(final BuildContext context, final String tit
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.red,
backgroundColor: Colors.red,
),
onPressed: () {
Navigator.of(context).pop(false);
@ -18,7 +18,7 @@ Future<bool> showConfirmationDialog(final BuildContext context, final String tit
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.green,
backgroundColor: Colors.green,
),
onPressed: () {
Navigator.of(context).pop(true);

4
packages/neon/lib/src/utils/global_options.dart

@ -77,8 +77,8 @@ class GlobalOptions {
}
void dispose() {
_accountsIDsSubject.close();
_themeOLEDAsDarkEnabledSubject.close();
unawaited(_accountsIDsSubject.close());
unawaited(_themeOLEDAsDarkEnabledSubject.close());
for (final option in options) {
option.dispose();
}

11
packages/neon/lib/src/utils/push_utils.dart

@ -1,7 +1,7 @@
part of '../neon.dart';
class PushUtils {
static RSAKeypair loadRSAKeypair(final Storage storage) {
static Future<RSAKeypair> loadRSAKeypair(final Storage storage) async {
const keyDevicePrivateKey = 'device-private-key';
late RSAKeypair keypair;
@ -10,7 +10,7 @@ class PushUtils {
// The key size has to be 2048, other sizes are not accepted by Nextcloud (at the moment at least)
// ignore: avoid_redundant_argument_values
keypair = RSAKeypair.fromRandom(keySize: 2048);
storage.setString(keyDevicePrivateKey, keypair.privateKey.toPEM());
await storage.setString(keyDevicePrivateKey, keypair.privateKey.toPEM());
} else {
keypair = RSAKeypair(RSAPrivateKey.fromPEM(storage.getString(keyDevicePrivateKey)!));
}
@ -47,7 +47,7 @@ class PushUtils {
);
final sharedPreferences = await SharedPreferences.getInstance();
final keypair = loadRSAKeypair(Storage('notifications', sharedPreferences));
final keypair = await loadRSAKeypair(Storage('notifications', sharedPreferences));
final data = json.decode(utf8.decode(message)) as Map<String, dynamic>;
final notification = NextcloudNotification(
accountID: instance,
@ -69,7 +69,8 @@ class PushUtils {
return;
}
final parts = (await findSystemLocale()).split('_').map((final a) => a.split('.')).reduce((a, b) => [...a, ...b]);
final parts =
(await findSystemLocale()).split('_').map((final a) => a.split('.')).reduce((final a, final b) => [...a, ...b]);
final localizations = await AppLocalizations.delegate.load(Locale(parts[0], parts.length > 1 ? parts[1] : null));
final platform = await getNeonPlatform();
@ -121,5 +122,5 @@ class PushUtils {
final String instance,
final NextcloudNotification notification,
) =>
sha256.convert(utf8.encode('$instance${notification.subject.nid!}')).bytes.reduce((a, b) => a + b);
sha256.convert(utf8.encode('$instance${notification.subject.nid!}')).bytes.reduce((final a, final b) => a + b);
}

4
packages/neon/lib/src/utils/theme.dart

@ -118,8 +118,8 @@ ThemeData getThemeFromNextcloudTheme(
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
onPrimary: onPrimaryColor,
primary: primaryColor,
foregroundColor: onPrimaryColor,
backgroundColor: primaryColor,
).copyWith(
elevation: ButtonStyleButton.allOrNull(0),
),

2
packages/neon/lib/src/widgets/image_wrapper.dart

@ -17,7 +17,7 @@ class ImageWrapper extends StatelessWidget {
final BorderRadius? borderRadius;
@override
Widget build(BuildContext context) => SizedBox(
Widget build(final BuildContext context) => SizedBox(
width: width,
height: height,
child: DecoratedBox(

78
packages/neon/pubspec.lock

@ -7,21 +7,21 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "45.0.0"
version: "46.0.0"
analyzer:
dependency: "direct overridden"
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "4.5.0"
version: "4.6.0"
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.11"
version: "3.3.0"
args:
dependency: transitive
description:
@ -42,7 +42,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
version: "2.9.0"
boolean_selector:
dependency: transitive
description:
@ -105,14 +105,14 @@ packages:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.4.0"
version: "8.4.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
charcode:
dependency: transitive
description:
@ -133,7 +133,7 @@ packages:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
code_builder:
dependency: transitive
description:
@ -168,7 +168,7 @@ packages:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
crypton:
dependency: transitive
description:
@ -203,7 +203,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.3.1"
ffi:
dependency: transitive
description:
@ -269,7 +269,7 @@ packages:
name: flutter_file_dialog
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "2.3.1"
flutter_html:
dependency: "direct main"
description:
@ -283,7 +283,7 @@ packages:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "9.7.0"
version: "9.9.0"
flutter_local_notifications_linux:
dependency: transitive
description:
@ -309,14 +309,14 @@ packages:
name: flutter_markdown
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.10+3"
version: "0.6.10+5"
flutter_native_splash:
dependency: "direct main"
description:
name: flutter_native_splash
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.7"
version: "2.2.8"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -330,14 +330,14 @@ packages:
name: flutter_rx_bloc
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
version: "5.0.1"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
version: "1.1.4"
flutter_test:
dependency: "direct dev"
description: flutter
@ -518,14 +518,14 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
version: "0.12.12"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
version: "0.1.5"
material_design_icons_flutter:
dependency: "direct main"
description:
@ -546,7 +546,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
mime:
dependency: transitive
description:
@ -646,7 +646,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
path_drawing:
dependency: transitive
description:
@ -674,7 +674,7 @@ packages:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.18"
version: "2.0.20"
path_provider_ios:
dependency: transitive
description:
@ -807,7 +807,7 @@ packages:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
queue:
dependency: "direct main"
description:
@ -835,7 +835,7 @@ packages:
name: quick_actions_ios
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.0+13"
version: "0.6.0+14"
quick_actions_platform_interface:
dependency: transitive
description:
@ -961,7 +961,7 @@ packages:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
shared_preferences_web:
dependency: transitive
description:
@ -1029,7 +1029,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
version: "1.9.0"
sqflite:
dependency: "direct main"
description:
@ -1057,7 +1057,7 @@ packages:
name: sqlite3
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.2"
version: "1.8.0"
stack_trace:
dependency: transitive
description:
@ -1085,14 +1085,14 @@ packages:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
sync_http:
dependency: transitive
description:
name: sync_http
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.3.1"
synchronized:
dependency: transitive
description:
@ -1106,14 +1106,14 @@ packages:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9"
version: "0.4.12"
timezone:
dependency: transitive
description:
@ -1134,14 +1134,14 @@ packages:
name: tray_manager
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.8"
version: "0.2.0"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.3.1"
unifiedpush:
dependency: "direct main"
description:
@ -1239,7 +1239,7 @@ packages:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "8.2.2"
version: "9.0.0"
wakelock:
dependency: "direct main"
description:
@ -1309,14 +1309,14 @@ packages:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.3"
version: "2.9.5"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.1"
version: "1.9.2"
webview_flutter_wkwebview:
dependency: transitive
description:
@ -1337,14 +1337,14 @@ packages:
name: window_manager
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.5"
version: "0.2.7"
xdg_directories:
dependency: "direct main"
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+1"
version: "0.2.0+2"
xml:
dependency: transitive
description:
@ -1360,5 +1360,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0"
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0"

11
packages/neon/pubspec.yaml

@ -3,8 +3,8 @@ version: 1.0.0
publish_to: 'none'
environment:
sdk: '>=2.17.0 <3.0.0'
flutter: '>=3.0.0'
sdk: '>=2.18.0 <3.0.0'
flutter: '>=3.3.0'
dependencies:
collection: ^1.16.0
@ -23,7 +23,7 @@ dependencies:
sdk: flutter
flutter_markdown: ^0.6.10+2
flutter_native_splash: ^2.2.0+1
flutter_rx_bloc: ^4.0.0
flutter_rx_bloc: ^5.0.1
flutter_svg: ^1.0.3
html: ^0.15.0
http: ^0.13.4
@ -52,7 +52,7 @@ dependencies:
path: ../sort_box
sqflite: ^2.0.2+1
sqflite_common_ffi: ^2.1.1
tray_manager: ^0.1.8
tray_manager: ^0.2.0
unifiedpush: ^4.0.1
url_launcher: ^6.0.18
wakelock: ^0.6.1+2
@ -75,9 +75,6 @@ dev_dependencies:
ref: f29382f
rx_bloc_generator: ^6.0.0
dependency_overrides:
analyzer: ^4.0.0 # https://github.com/dart-lang/language/issues/1855#issuecomment-1013212536
flutter:
uses-material-design: true
assets:

4
packages/nextcloud/analysis_options.yaml

@ -1,9 +1,5 @@
include: package:nit_picking/dart.yaml
linter:
rules:
prefer_final_parameters: false # Disabled until super.X is no longer complained about in constructors
analyzer:
exclude:
- 'lib/src/clients/common'

2
packages/nextcloud/lib/src/clients/notifications.dart

@ -26,7 +26,7 @@ class NextcloudNotificationsClient extends DefaultApi {
@override
@Deprecated('Use registerDeviceAtServer instead')
Future<NotificationsPushServerRegistrationResponse?> registerDevice(
NotificationsPushServerDevice notificationsPushServerDevice,
final NotificationsPushServerDevice notificationsPushServerDevice,
) =>
throw Exception('Use registerDeviceAtServer instead');

2
packages/nextcloud/pubspec.yaml

@ -2,7 +2,7 @@ name: nextcloud
version: 1.0.0
environment:
sdk: '>=2.17.0 <3.0.0'
sdk: '>=2.18.0 <3.0.0'
dependencies:
crypto: ^3.0.1

4
packages/nextcloud_push_proxy/analysis_options.yaml

@ -1,5 +1 @@
include: package:nit_picking/dart.yaml
linter:
rules:
prefer_final_parameters: false # Disabled until super.X is no longer complained about in constructors

6
packages/nextcloud_push_proxy/lib/nextcloud_push_proxy.dart

@ -41,7 +41,7 @@ class NextcloudPushProxy {
..post('/notifications', _notificationsHandler)
..get('/health', (final _) async => Response.ok(''));
Future<Response> _devicesHandler(Request request) async {
Future<Response> _devicesHandler(final Request request) async {
final data = Uri(query: await request.readAsString()).queryParameters;
_onNewDeviceController.add(
PushProxyDevice(
@ -54,7 +54,7 @@ class NextcloudPushProxy {
return Response.ok('');
}
Future<Response> _notificationsHandler(Request request) async {
Future<Response> _notificationsHandler(final Request request) async {
final data = Uri(query: await request.readAsString()).queryParameters;
for (final notification in data.values) {
final notificationData = json.decode(notification) as Map<String, dynamic>;
@ -119,7 +119,7 @@ class PushProxyDevice {
required this.userPublicKey,
});
factory PushProxyDevice.fromJson(Map<String, dynamic> data) => PushProxyDevice(
factory PushProxyDevice.fromJson(final Map<String, dynamic> data) => PushProxyDevice(
pushToken: data['pushToken'] as String,
deviceIdentifier: data['deviceIdentifier'] as String,
deviceIdentifierSignature: data['deviceIdentifierSignature'] as String,

2
packages/nextcloud_push_proxy/pubspec.yaml

@ -2,7 +2,7 @@ name: nextcloud_push_proxy
version: 1.0.0
environment:
sdk: '>=2.17.0 <3.0.0'
sdk: '>=2.18.0 <3.0.0'
dependencies:
shelf: ^1.3.1

4
packages/settings/analysis_options.yaml

@ -1,5 +1 @@
include: package:nit_picking/flutter.yaml
linter:
rules:
prefer_final_parameters: false # Disabled until super.X is no longer complained about in constructors

2
packages/settings/lib/settings.dart

@ -1,5 +1,7 @@
library settings;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intersperse/intersperse.dart';
import 'package:rxdart/rxdart.dart';

4
packages/settings/lib/src/options/option.dart

@ -47,8 +47,8 @@ abstract class Option<T> {
}
void dispose() {
stream.close();
enabled.close();
unawaited(stream.close());
unawaited(enabled.close());
}
Future set(final T value);

4
packages/settings/lib/src/options/select_option.dart

@ -11,6 +11,7 @@ class SelectOption<T> extends Option<T> {
super.enabled,
}) {
stream = BehaviorSubject();
unawaited(
values.first.then((final vs) async {
final valueStr = storage.getString(key);
T? initialValue;
@ -19,7 +20,8 @@ class SelectOption<T> extends Option<T> {
initialValue = _fromString(vs, valueStr);
}
stream.add(initialValue ?? await defaultValue.first);
});
}),
);
}
T? _fromString(final Map<T, LabelBuilder> vs, final String? valueStr) {

4
packages/settings/pubspec.yaml

@ -2,8 +2,8 @@ name: settings
version: 1.0.0
environment:
sdk: '>=2.17.0 <3.0.0'
flutter: '>=3.0.0'
sdk: '>=2.18.0 <3.0.0'
flutter: '>=3.3.0'
dependencies:
flutter:

2
packages/sort_box/pubspec.yaml

@ -2,7 +2,7 @@ name: sort_box
version: 1.0.0
environment:
sdk: '>=2.17.0 <3.0.0'
sdk: '>=2.18.0 <3.0.0'
dev_dependencies:
nit_picking:

4
packages/spec_templates/analysis_options.yaml

@ -1,5 +1 @@
include: package:nit_picking/dart.yaml
analyzer:
errors:
import_of_legacy_library_into_null_safe: ignore

2
packages/spec_templates/pubspec.yaml

@ -3,7 +3,7 @@ version: 1.0.0
publish_to: 'none'
environment:
sdk: '>=2.17.0 <3.0.0'
sdk: '>=2.18.0 <3.0.0'
dependencies:
path: ^1.8.1

Loading…
Cancel
Save