Nikolas Rimikis
1 year ago
26 changed files with 862 additions and 798 deletions
@ -0,0 +1,27 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
import 'package:neon/src/widgets/dialog.dart'; |
||||||
|
|
||||||
|
Future<bool> showConfirmationDialog(final BuildContext context, final String title) async => |
||||||
|
await showDialog<bool>( |
||||||
|
context: context, |
||||||
|
builder: (final context) => NeonConfirmationDialog( |
||||||
|
title: title, |
||||||
|
), |
||||||
|
) ?? |
||||||
|
false; |
||||||
|
|
||||||
|
Future<String?> showRenameDialog({ |
||||||
|
required final BuildContext context, |
||||||
|
required final String title, |
||||||
|
required final String value, |
||||||
|
final Key? key, |
||||||
|
}) async => |
||||||
|
showDialog<String?>( |
||||||
|
context: context, |
||||||
|
builder: (final context) => RenameDialog( |
||||||
|
title: title, |
||||||
|
value: value, |
||||||
|
key: key, |
||||||
|
), |
||||||
|
); |
@ -1,83 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:neon/src/utils/validators.dart'; |
|
||||||
import 'package:neon/src/widgets/dialog.dart'; |
|
||||||
|
|
||||||
Future<String?> showRenameDialog({ |
|
||||||
required final BuildContext context, |
|
||||||
required final String title, |
|
||||||
required final String value, |
|
||||||
final Key? key, |
|
||||||
}) async => |
|
||||||
showDialog<String?>( |
|
||||||
context: context, |
|
||||||
builder: (final context) => _RenameDialog( |
|
||||||
title: title, |
|
||||||
value: value, |
|
||||||
key: key, |
|
||||||
), |
|
||||||
); |
|
||||||
|
|
||||||
class _RenameDialog extends StatefulWidget { |
|
||||||
const _RenameDialog({ |
|
||||||
required this.title, |
|
||||||
required this.value, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final String title; |
|
||||||
final String value; |
|
||||||
|
|
||||||
@override |
|
||||||
State<_RenameDialog> createState() => _RenameDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _RenameDialogState extends State<_RenameDialog> { |
|
||||||
final formKey = GlobalKey<FormState>(); |
|
||||||
|
|
||||||
final controller = TextEditingController(); |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
controller.text = widget.value; |
|
||||||
super.initState(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
controller.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
void submit() { |
|
||||||
if (formKey.currentState!.validate()) { |
|
||||||
Navigator.of(context).pop(controller.text); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => NeonDialog( |
|
||||||
title: Text(widget.title), |
|
||||||
children: [ |
|
||||||
Form( |
|
||||||
key: formKey, |
|
||||||
child: Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.end, |
|
||||||
children: [ |
|
||||||
TextFormField( |
|
||||||
autofocus: true, |
|
||||||
controller: controller, |
|
||||||
validator: (final input) => validateNotEmpty(context, input), |
|
||||||
onFieldSubmitted: (final _) { |
|
||||||
submit(); |
|
||||||
}, |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: submit, |
|
||||||
child: Text(widget.title), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -1,64 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:meta/meta.dart'; |
|
||||||
import 'package:neon/src/blocs/accounts.dart'; |
|
||||||
import 'package:neon/src/theme/dialog.dart'; |
|
||||||
import 'package:neon/src/utils/provider.dart'; |
|
||||||
import 'package:neon/src/widgets/account_tile.dart'; |
|
||||||
|
|
||||||
@internal |
|
||||||
class NeonAccountSelectionDialog extends StatelessWidget { |
|
||||||
const NeonAccountSelectionDialog({ |
|
||||||
this.highlightActiveAccount = false, |
|
||||||
this.children, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final bool highlightActiveAccount; |
|
||||||
final List<Widget>? children; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) { |
|
||||||
final accountsBloc = NeonProvider.of<AccountsBloc>(context); |
|
||||||
final accounts = accountsBloc.accounts.value; |
|
||||||
final activeAccount = accountsBloc.activeAccount.value!; |
|
||||||
|
|
||||||
final sortedAccounts = List.of(accounts) |
|
||||||
..removeWhere((final account) => account.id == activeAccount.id) |
|
||||||
..insert(0, activeAccount); |
|
||||||
|
|
||||||
final tiles = sortedAccounts |
|
||||||
.map<Widget>( |
|
||||||
(final account) => NeonAccountTile( |
|
||||||
account: account, |
|
||||||
trailing: highlightActiveAccount && account.id == activeAccount.id ? const Icon(Icons.check_circle) : null, |
|
||||||
onTap: () { |
|
||||||
Navigator.of(context).pop(account); |
|
||||||
}, |
|
||||||
), |
|
||||||
) |
|
||||||
.toList(); |
|
||||||
if (highlightActiveAccount && accounts.length > 1) { |
|
||||||
tiles.insert(1, const Divider()); |
|
||||||
} |
|
||||||
|
|
||||||
final body = SingleChildScrollView( |
|
||||||
child: Column( |
|
||||||
mainAxisSize: MainAxisSize.min, |
|
||||||
children: [ |
|
||||||
...tiles, |
|
||||||
...?children, |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
|
|
||||||
return Dialog( |
|
||||||
child: IntrinsicHeight( |
|
||||||
child: Container( |
|
||||||
padding: const EdgeInsets.all(24), |
|
||||||
constraints: NeonDialogTheme.of(context).constraints, |
|
||||||
child: body, |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,9 +1,8 @@ |
|||||||
export 'package:neon/l10n/localizations.dart'; |
export 'package:neon/l10n/localizations.dart'; |
||||||
export 'package:neon/src/utils/app_route.dart'; |
export 'package:neon/src/utils/app_route.dart'; |
||||||
export 'package:neon/src/utils/confirmation_dialog.dart'; |
export 'package:neon/src/utils/dialog.dart'; |
||||||
export 'package:neon/src/utils/exceptions.dart'; |
export 'package:neon/src/utils/exceptions.dart'; |
||||||
export 'package:neon/src/utils/hex_color.dart'; |
export 'package:neon/src/utils/hex_color.dart'; |
||||||
export 'package:neon/src/utils/provider.dart'; |
export 'package:neon/src/utils/provider.dart'; |
||||||
export 'package:neon/src/utils/rename_dialog.dart'; |
|
||||||
export 'package:neon/src/utils/request_manager.dart' hide Cache; |
export 'package:neon/src/utils/request_manager.dart' hide Cache; |
||||||
export 'package:neon/src/utils/validators.dart'; |
export 'package:neon/src/utils/validators.dart'; |
||||||
|
@ -1,121 +0,0 @@ |
|||||||
part of '../neon_files.dart'; |
|
||||||
|
|
||||||
class FilesChooseCreateDialog extends StatefulWidget { |
|
||||||
const FilesChooseCreateDialog({ |
|
||||||
required this.bloc, |
|
||||||
required this.basePath, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final FilesBloc bloc; |
|
||||||
final PathUri basePath; |
|
||||||
|
|
||||||
@override |
|
||||||
State<FilesChooseCreateDialog> createState() => _FilesChooseCreateDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> { |
|
||||||
Future<void> uploadFromPick(final FileType type) async { |
|
||||||
final result = await FilePicker.platform.pickFiles( |
|
||||||
allowMultiple: true, |
|
||||||
type: type, |
|
||||||
); |
|
||||||
if (result != null) { |
|
||||||
for (final file in result.files) { |
|
||||||
await upload(File(file.path!)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> upload(final File file) async { |
|
||||||
final sizeWarning = widget.bloc.options.uploadSizeWarning.value; |
|
||||||
if (sizeWarning != null) { |
|
||||||
final stat = file.statSync(); |
|
||||||
if (stat.size > sizeWarning) { |
|
||||||
if (!(await showConfirmationDialog( |
|
||||||
context, |
|
||||||
FilesLocalizations.of(context).uploadConfirmSizeWarning( |
|
||||||
filesize(sizeWarning), |
|
||||||
filesize(stat.size), |
|
||||||
), |
|
||||||
))) { |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
widget.bloc.uploadFile( |
|
||||||
widget.basePath.join(PathUri.parse(p.basename(file.path))), |
|
||||||
file.path, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => NeonDialog( |
|
||||||
children: [ |
|
||||||
ListTile( |
|
||||||
leading: Icon( |
|
||||||
MdiIcons.filePlus, |
|
||||||
color: Theme.of(context).colorScheme.primary, |
|
||||||
), |
|
||||||
title: Text(FilesLocalizations.of(context).uploadFiles), |
|
||||||
onTap: () async { |
|
||||||
await uploadFromPick(FileType.any); |
|
||||||
|
|
||||||
if (mounted) { |
|
||||||
Navigator.of(context).pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
leading: Icon( |
|
||||||
MdiIcons.fileImagePlus, |
|
||||||
color: Theme.of(context).colorScheme.primary, |
|
||||||
), |
|
||||||
title: Text(FilesLocalizations.of(context).uploadImages), |
|
||||||
onTap: () async { |
|
||||||
await uploadFromPick(FileType.image); |
|
||||||
|
|
||||||
if (mounted) { |
|
||||||
Navigator.of(context).pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
if (NeonPlatform.instance.canUseCamera) ...[ |
|
||||||
ListTile( |
|
||||||
leading: Icon( |
|
||||||
MdiIcons.cameraPlus, |
|
||||||
color: Theme.of(context).colorScheme.primary, |
|
||||||
), |
|
||||||
title: Text(FilesLocalizations.of(context).uploadCamera), |
|
||||||
onTap: () async { |
|
||||||
Navigator.of(context).pop(); |
|
||||||
|
|
||||||
final picker = ImagePicker(); |
|
||||||
final result = await picker.pickImage(source: ImageSource.camera); |
|
||||||
if (result != null) { |
|
||||||
await upload(File(result.path)); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
], |
|
||||||
ListTile( |
|
||||||
leading: Icon( |
|
||||||
MdiIcons.folderPlus, |
|
||||||
color: Theme.of(context).colorScheme.primary, |
|
||||||
), |
|
||||||
title: Text(FilesLocalizations.of(context).folderCreate), |
|
||||||
onTap: () async { |
|
||||||
Navigator.of(context).pop(); |
|
||||||
|
|
||||||
final result = await showDialog<String>( |
|
||||||
context: context, |
|
||||||
builder: (final context) => const FilesCreateFolderDialog(), |
|
||||||
); |
|
||||||
if (result != null) { |
|
||||||
widget.bloc.browser.createFolder(widget.basePath.join(PathUri.parse(result))); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
part of '../neon_files.dart'; |
|
||||||
|
|
||||||
class FilesChooseFolderDialog extends StatelessWidget { |
|
||||||
const FilesChooseFolderDialog({ |
|
||||||
required this.bloc, |
|
||||||
required this.filesBloc, |
|
||||||
required this.originalPath, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final FilesBrowserBloc bloc; |
|
||||||
final FilesBloc filesBloc; |
|
||||||
|
|
||||||
final PathUri originalPath; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => AlertDialog( |
|
||||||
title: Text(FilesLocalizations.of(context).folderChoose), |
|
||||||
contentPadding: EdgeInsets.zero, |
|
||||||
content: SizedBox( |
|
||||||
width: double.maxFinite, |
|
||||||
child: Column( |
|
||||||
children: [ |
|
||||||
Expanded( |
|
||||||
child: FilesBrowserView( |
|
||||||
bloc: bloc, |
|
||||||
filesBloc: filesBloc, |
|
||||||
mode: FilesBrowserMode.selectDirectory, |
|
||||||
), |
|
||||||
), |
|
||||||
StreamBuilder<PathUri>( |
|
||||||
stream: bloc.uri, |
|
||||||
builder: (final context, final uriSnapshot) => uriSnapshot.hasData |
|
||||||
? Container( |
|
||||||
margin: const EdgeInsets.all(10), |
|
||||||
child: Row( |
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
||||||
children: [ |
|
||||||
ElevatedButton( |
|
||||||
onPressed: () async { |
|
||||||
final result = await showDialog<String>( |
|
||||||
context: context, |
|
||||||
builder: (final context) => const FilesCreateFolderDialog(), |
|
||||||
); |
|
||||||
if (result != null) { |
|
||||||
bloc.createFolder(uriSnapshot.requireData.join(PathUri.parse(result))); |
|
||||||
} |
|
||||||
}, |
|
||||||
child: Text(FilesLocalizations.of(context).folderCreate), |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: originalPath != uriSnapshot.requireData |
|
||||||
? () => Navigator.of(context).pop(uriSnapshot.requireData) |
|
||||||
: null, |
|
||||||
child: Text(FilesLocalizations.of(context).folderChoose), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
) |
|
||||||
: const SizedBox(), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
@ -1,58 +0,0 @@ |
|||||||
part of '../neon_files.dart'; |
|
||||||
|
|
||||||
class FilesCreateFolderDialog extends StatefulWidget { |
|
||||||
const FilesCreateFolderDialog({ |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
@override |
|
||||||
State<FilesCreateFolderDialog> createState() => _FilesCreateFolderDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _FilesCreateFolderDialogState extends State<FilesCreateFolderDialog> { |
|
||||||
final formKey = GlobalKey<FormState>(); |
|
||||||
|
|
||||||
final controller = TextEditingController(); |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
controller.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
void submit() { |
|
||||||
if (formKey.currentState!.validate()) { |
|
||||||
Navigator.of(context).pop(controller.text); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => NeonDialog( |
|
||||||
title: Text(FilesLocalizations.of(context).folderCreate), |
|
||||||
children: [ |
|
||||||
Form( |
|
||||||
key: formKey, |
|
||||||
child: Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.end, |
|
||||||
children: [ |
|
||||||
TextFormField( |
|
||||||
controller: controller, |
|
||||||
decoration: InputDecoration( |
|
||||||
hintText: FilesLocalizations.of(context).folderName, |
|
||||||
), |
|
||||||
autofocus: true, |
|
||||||
validator: (final input) => validateNotEmpty(context, input), |
|
||||||
onFieldSubmitted: (final _) { |
|
||||||
submit(); |
|
||||||
}, |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: submit, |
|
||||||
child: Text(FilesLocalizations.of(context).folderCreate), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -0,0 +1,257 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart'; |
||||||
|
import 'package:filesize/filesize.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; |
||||||
|
import 'package:image_picker/image_picker.dart'; |
||||||
|
import 'package:neon/platform.dart'; |
||||||
|
import 'package:neon/utils.dart'; |
||||||
|
import 'package:neon/widgets.dart'; |
||||||
|
import 'package:neon_files/l10n/localizations.dart'; |
||||||
|
import 'package:neon_files/neon_files.dart'; |
||||||
|
import 'package:nextcloud/nextcloud.dart'; |
||||||
|
import 'package:path/path.dart' as p; |
||||||
|
import 'package:universal_io/io.dart'; |
||||||
|
|
||||||
|
class FilesChooseCreateDialog extends StatefulWidget { |
||||||
|
const FilesChooseCreateDialog({ |
||||||
|
required this.bloc, |
||||||
|
required this.basePath, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final FilesBloc bloc; |
||||||
|
final PathUri basePath; |
||||||
|
|
||||||
|
@override |
||||||
|
State<FilesChooseCreateDialog> createState() => _FilesChooseCreateDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> { |
||||||
|
Future<void> uploadFromPick(final FileType type) async { |
||||||
|
final result = await FilePicker.platform.pickFiles( |
||||||
|
allowMultiple: true, |
||||||
|
type: type, |
||||||
|
); |
||||||
|
if (result != null) { |
||||||
|
for (final file in result.files) { |
||||||
|
await upload(File(file.path!)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> upload(final File file) async { |
||||||
|
final sizeWarning = widget.bloc.options.uploadSizeWarning.value; |
||||||
|
if (sizeWarning != null) { |
||||||
|
final stat = file.statSync(); |
||||||
|
if (stat.size > sizeWarning) { |
||||||
|
if (!(await showConfirmationDialog( |
||||||
|
context, |
||||||
|
FilesLocalizations.of(context).uploadConfirmSizeWarning( |
||||||
|
filesize(sizeWarning), |
||||||
|
filesize(stat.size), |
||||||
|
), |
||||||
|
))) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
widget.bloc.uploadFile( |
||||||
|
widget.basePath.join(PathUri.parse(p.basename(file.path))), |
||||||
|
file.path, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => NeonDialog( |
||||||
|
children: [ |
||||||
|
ListTile( |
||||||
|
leading: Icon( |
||||||
|
MdiIcons.filePlus, |
||||||
|
color: Theme.of(context).colorScheme.primary, |
||||||
|
), |
||||||
|
title: Text(FilesLocalizations.of(context).uploadFiles), |
||||||
|
onTap: () async { |
||||||
|
await uploadFromPick(FileType.any); |
||||||
|
|
||||||
|
if (mounted) { |
||||||
|
Navigator.of(context).pop(); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
ListTile( |
||||||
|
leading: Icon( |
||||||
|
MdiIcons.fileImagePlus, |
||||||
|
color: Theme.of(context).colorScheme.primary, |
||||||
|
), |
||||||
|
title: Text(FilesLocalizations.of(context).uploadImages), |
||||||
|
onTap: () async { |
||||||
|
await uploadFromPick(FileType.image); |
||||||
|
|
||||||
|
if (mounted) { |
||||||
|
Navigator.of(context).pop(); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
if (NeonPlatform.instance.canUseCamera) ...[ |
||||||
|
ListTile( |
||||||
|
leading: Icon( |
||||||
|
MdiIcons.cameraPlus, |
||||||
|
color: Theme.of(context).colorScheme.primary, |
||||||
|
), |
||||||
|
title: Text(FilesLocalizations.of(context).uploadCamera), |
||||||
|
onTap: () async { |
||||||
|
Navigator.of(context).pop(); |
||||||
|
|
||||||
|
final picker = ImagePicker(); |
||||||
|
final result = await picker.pickImage(source: ImageSource.camera); |
||||||
|
if (result != null) { |
||||||
|
await upload(File(result.path)); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
ListTile( |
||||||
|
leading: Icon( |
||||||
|
MdiIcons.folderPlus, |
||||||
|
color: Theme.of(context).colorScheme.primary, |
||||||
|
), |
||||||
|
title: Text(FilesLocalizations.of(context).folderCreate), |
||||||
|
onTap: () async { |
||||||
|
Navigator.of(context).pop(); |
||||||
|
|
||||||
|
final result = await showDialog<String>( |
||||||
|
context: context, |
||||||
|
builder: (final context) => const FilesCreateFolderDialog(), |
||||||
|
); |
||||||
|
if (result != null) { |
||||||
|
widget.bloc.browser.createFolder(widget.basePath.join(PathUri.parse(result))); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
class FilesChooseFolderDialog extends StatelessWidget { |
||||||
|
const FilesChooseFolderDialog({ |
||||||
|
required this.bloc, |
||||||
|
required this.filesBloc, |
||||||
|
required this.originalPath, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final FilesBrowserBloc bloc; |
||||||
|
final FilesBloc filesBloc; |
||||||
|
|
||||||
|
final PathUri originalPath; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => AlertDialog( |
||||||
|
title: Text(FilesLocalizations.of(context).folderChoose), |
||||||
|
contentPadding: EdgeInsets.zero, |
||||||
|
content: SizedBox( |
||||||
|
width: double.maxFinite, |
||||||
|
child: Column( |
||||||
|
children: [ |
||||||
|
Expanded( |
||||||
|
child: FilesBrowserView( |
||||||
|
bloc: bloc, |
||||||
|
filesBloc: filesBloc, |
||||||
|
mode: FilesBrowserMode.selectDirectory, |
||||||
|
), |
||||||
|
), |
||||||
|
StreamBuilder<PathUri>( |
||||||
|
stream: bloc.uri, |
||||||
|
builder: (final context, final uriSnapshot) => uriSnapshot.hasData |
||||||
|
? Container( |
||||||
|
margin: const EdgeInsets.all(10), |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||||
|
children: [ |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () async { |
||||||
|
final result = await showDialog<String>( |
||||||
|
context: context, |
||||||
|
builder: (final context) => const FilesCreateFolderDialog(), |
||||||
|
); |
||||||
|
if (result != null) { |
||||||
|
bloc.createFolder(uriSnapshot.requireData.join(PathUri.parse(result))); |
||||||
|
} |
||||||
|
}, |
||||||
|
child: Text(FilesLocalizations.of(context).folderCreate), |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: originalPath != uriSnapshot.requireData |
||||||
|
? () => Navigator.of(context).pop(uriSnapshot.requireData) |
||||||
|
: null, |
||||||
|
child: Text(FilesLocalizations.of(context).folderChoose), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
) |
||||||
|
: const SizedBox(), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
class FilesCreateFolderDialog extends StatefulWidget { |
||||||
|
const FilesCreateFolderDialog({ |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
@override |
||||||
|
State<FilesCreateFolderDialog> createState() => _FilesCreateFolderDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _FilesCreateFolderDialogState extends State<FilesCreateFolderDialog> { |
||||||
|
final formKey = GlobalKey<FormState>(); |
||||||
|
|
||||||
|
final controller = TextEditingController(); |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
controller.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
void submit() { |
||||||
|
if (formKey.currentState!.validate()) { |
||||||
|
Navigator.of(context).pop(controller.text); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => NeonDialog( |
||||||
|
title: Text(FilesLocalizations.of(context).folderCreate), |
||||||
|
children: [ |
||||||
|
Form( |
||||||
|
key: formKey, |
||||||
|
child: Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
TextFormField( |
||||||
|
controller: controller, |
||||||
|
decoration: InputDecoration( |
||||||
|
hintText: FilesLocalizations.of(context).folderName, |
||||||
|
), |
||||||
|
autofocus: true, |
||||||
|
validator: (final input) => validateNotEmpty(context, input), |
||||||
|
onFieldSubmitted: (final _) { |
||||||
|
submit(); |
||||||
|
}, |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: submit, |
||||||
|
child: Text(FilesLocalizations.of(context).folderCreate), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
@ -1,108 +0,0 @@ |
|||||||
part of '../neon_news.dart'; |
|
||||||
|
|
||||||
class NewsAddFeedDialog extends StatefulWidget { |
|
||||||
const NewsAddFeedDialog({ |
|
||||||
required this.bloc, |
|
||||||
this.folderID, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final NewsBloc bloc; |
|
||||||
final int? folderID; |
|
||||||
|
|
||||||
@override |
|
||||||
State<NewsAddFeedDialog> createState() => _NewsAddFeedDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> { |
|
||||||
final formKey = GlobalKey<FormState>(); |
|
||||||
final controller = TextEditingController(); |
|
||||||
|
|
||||||
news.Folder? folder; |
|
||||||
|
|
||||||
void submit() { |
|
||||||
if (formKey.currentState!.validate()) { |
|
||||||
Navigator.of(context).pop((controller.text, widget.folderID ?? folder?.id)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
super.initState(); |
|
||||||
|
|
||||||
unawaited( |
|
||||||
Clipboard.getData(Clipboard.kTextPlain).then((final clipboardContent) { |
|
||||||
if (clipboardContent != null && clipboardContent.text != null) { |
|
||||||
final uri = Uri.tryParse(clipboardContent.text!); |
|
||||||
if (uri != null && (uri.scheme == 'http' || uri.scheme == 'https')) { |
|
||||||
controller.text = clipboardContent.text!; |
|
||||||
} |
|
||||||
} |
|
||||||
}), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
controller.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => ResultBuilder<List<news.Folder>>.behaviorSubject( |
|
||||||
subject: widget.bloc.folders, |
|
||||||
builder: (final context, final folders) => NeonDialog( |
|
||||||
title: Text(NewsLocalizations.of(context).feedAdd), |
|
||||||
children: [ |
|
||||||
Form( |
|
||||||
key: formKey, |
|
||||||
child: Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.end, |
|
||||||
children: [ |
|
||||||
TextFormField( |
|
||||||
autofocus: true, |
|
||||||
controller: controller, |
|
||||||
decoration: const InputDecoration( |
|
||||||
hintText: 'https://...', |
|
||||||
), |
|
||||||
keyboardType: TextInputType.url, |
|
||||||
validator: (final input) => validateHttpUrl(context, input), |
|
||||||
onFieldSubmitted: (final _) { |
|
||||||
submit(); |
|
||||||
}, |
|
||||||
), |
|
||||||
if (widget.folderID == null) ...[ |
|
||||||
Center( |
|
||||||
child: NeonError( |
|
||||||
folders.error, |
|
||||||
onRetry: widget.bloc.refresh, |
|
||||||
), |
|
||||||
), |
|
||||||
Center( |
|
||||||
child: NeonLinearProgressIndicator( |
|
||||||
visible: folders.isLoading, |
|
||||||
), |
|
||||||
), |
|
||||||
if (folders.hasData) ...[ |
|
||||||
NewsFolderSelect( |
|
||||||
folders: folders.requireData, |
|
||||||
value: folder, |
|
||||||
onChanged: (final f) { |
|
||||||
setState(() { |
|
||||||
folder = f; |
|
||||||
}); |
|
||||||
}, |
|
||||||
), |
|
||||||
], |
|
||||||
], |
|
||||||
ElevatedButton( |
|
||||||
onPressed: folders.hasData ? submit : null, |
|
||||||
child: Text(NewsLocalizations.of(context).feedAdd), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
@ -1,58 +0,0 @@ |
|||||||
part of '../neon_news.dart'; |
|
||||||
|
|
||||||
class NewsCreateFolderDialog extends StatefulWidget { |
|
||||||
const NewsCreateFolderDialog({ |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
@override |
|
||||||
State<NewsCreateFolderDialog> createState() => _NewsCreateFolderDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _NewsCreateFolderDialogState extends State<NewsCreateFolderDialog> { |
|
||||||
final formKey = GlobalKey<FormState>(); |
|
||||||
|
|
||||||
final controller = TextEditingController(); |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
controller.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
void submit() { |
|
||||||
if (formKey.currentState!.validate()) { |
|
||||||
Navigator.of(context).pop(controller.text); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => NeonDialog( |
|
||||||
title: Text(NewsLocalizations.of(context).folderCreate), |
|
||||||
children: [ |
|
||||||
Form( |
|
||||||
key: formKey, |
|
||||||
child: Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.end, |
|
||||||
children: [ |
|
||||||
TextFormField( |
|
||||||
autofocus: true, |
|
||||||
controller: controller, |
|
||||||
decoration: InputDecoration( |
|
||||||
hintText: NewsLocalizations.of(context).folderCreateName, |
|
||||||
), |
|
||||||
validator: (final input) => validateNotEmpty(context, input), |
|
||||||
onFieldSubmitted: (final _) { |
|
||||||
submit(); |
|
||||||
}, |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: submit, |
|
||||||
child: Text(NewsLocalizations.of(context).folderCreate), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
part of '../neon_news.dart'; |
|
||||||
|
|
||||||
class NewsFeedShowURLDialog extends StatefulWidget { |
|
||||||
const NewsFeedShowURLDialog({ |
|
||||||
required this.feed, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final news.Feed feed; |
|
||||||
|
|
||||||
@override |
|
||||||
State<NewsFeedShowURLDialog> createState() => _NewsFeedShowURLDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _NewsFeedShowURLDialogState extends State<NewsFeedShowURLDialog> { |
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => AlertDialog( |
|
||||||
title: Text(widget.feed.url), |
|
||||||
actions: [ |
|
||||||
ElevatedButton( |
|
||||||
onPressed: () async { |
|
||||||
await Clipboard.setData( |
|
||||||
ClipboardData( |
|
||||||
text: widget.feed.url, |
|
||||||
), |
|
||||||
); |
|
||||||
if (mounted) { |
|
||||||
ScaffoldMessenger.of(context).showSnackBar( |
|
||||||
SnackBar( |
|
||||||
content: Text(NewsLocalizations.of(context).feedCopiedURL), |
|
||||||
), |
|
||||||
); |
|
||||||
Navigator.of(context).pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
child: Text(NewsLocalizations.of(context).feedCopyURL), |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: () { |
|
||||||
Navigator.of(context).pop(); |
|
||||||
}, |
|
||||||
child: Text(NewsLocalizations.of(context).actionClose), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
part of '../neon_news.dart'; |
|
||||||
|
|
||||||
class NewsFeedUpdateErrorDialog extends StatefulWidget { |
|
||||||
const NewsFeedUpdateErrorDialog({ |
|
||||||
required this.feed, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final news.Feed feed; |
|
||||||
|
|
||||||
@override |
|
||||||
State<NewsFeedUpdateErrorDialog> createState() => _NewsFeedUpdateErrorDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _NewsFeedUpdateErrorDialogState extends State<NewsFeedUpdateErrorDialog> { |
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => AlertDialog( |
|
||||||
title: Text(widget.feed.lastUpdateError!), |
|
||||||
actions: [ |
|
||||||
ElevatedButton( |
|
||||||
onPressed: () async { |
|
||||||
await Clipboard.setData( |
|
||||||
ClipboardData( |
|
||||||
text: widget.feed.lastUpdateError!, |
|
||||||
), |
|
||||||
); |
|
||||||
if (mounted) { |
|
||||||
ScaffoldMessenger.of(context).showSnackBar( |
|
||||||
SnackBar( |
|
||||||
content: Text(NewsLocalizations.of(context).feedCopiedErrorMessage), |
|
||||||
), |
|
||||||
); |
|
||||||
Navigator.of(context).pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
child: Text(NewsLocalizations.of(context).feedCopyErrorMessage), |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: () { |
|
||||||
Navigator.of(context).pop(); |
|
||||||
}, |
|
||||||
child: Text(NewsLocalizations.of(context).actionClose), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
part of '../neon_news.dart'; |
|
||||||
|
|
||||||
class NewsMoveFeedDialog extends StatefulWidget { |
|
||||||
const NewsMoveFeedDialog({ |
|
||||||
required this.folders, |
|
||||||
required this.feed, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final List<news.Folder> folders; |
|
||||||
final news.Feed feed; |
|
||||||
|
|
||||||
@override |
|
||||||
State<NewsMoveFeedDialog> createState() => _NewsMoveFeedDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _NewsMoveFeedDialogState extends State<NewsMoveFeedDialog> { |
|
||||||
final formKey = GlobalKey<FormState>(); |
|
||||||
|
|
||||||
news.Folder? folder; |
|
||||||
|
|
||||||
void submit() { |
|
||||||
if (formKey.currentState!.validate()) { |
|
||||||
Navigator.of(context).pop([folder?.id]); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => NeonDialog( |
|
||||||
title: Text(NewsLocalizations.of(context).feedMove), |
|
||||||
children: [ |
|
||||||
Form( |
|
||||||
key: formKey, |
|
||||||
child: Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.end, |
|
||||||
children: [ |
|
||||||
NewsFolderSelect( |
|
||||||
folders: widget.folders, |
|
||||||
value: widget.feed.folderId != null |
|
||||||
? widget.folders.singleWhere((final folder) => folder.id == widget.feed.folderId) |
|
||||||
: null, |
|
||||||
onChanged: (final f) { |
|
||||||
setState(() { |
|
||||||
folder = f; |
|
||||||
}); |
|
||||||
}, |
|
||||||
), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: submit, |
|
||||||
child: Text(NewsLocalizations.of(context).feedMove), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
@ -0,0 +1,320 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/services.dart'; |
||||||
|
import 'package:neon/blocs.dart'; |
||||||
|
import 'package:neon/utils.dart'; |
||||||
|
import 'package:neon/widgets.dart'; |
||||||
|
import 'package:neon_news/l10n/localizations.dart'; |
||||||
|
import 'package:neon_news/neon_news.dart'; |
||||||
|
import 'package:nextcloud/news.dart' as news; |
||||||
|
|
||||||
|
class NewsAddFeedDialog extends StatefulWidget { |
||||||
|
const NewsAddFeedDialog({ |
||||||
|
required this.bloc, |
||||||
|
this.folderID, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final NewsBloc bloc; |
||||||
|
final int? folderID; |
||||||
|
|
||||||
|
@override |
||||||
|
State<NewsAddFeedDialog> createState() => _NewsAddFeedDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> { |
||||||
|
final formKey = GlobalKey<FormState>(); |
||||||
|
final controller = TextEditingController(); |
||||||
|
|
||||||
|
news.Folder? folder; |
||||||
|
|
||||||
|
void submit() { |
||||||
|
if (formKey.currentState!.validate()) { |
||||||
|
Navigator.of(context).pop((controller.text, widget.folderID ?? folder?.id)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
|
||||||
|
unawaited( |
||||||
|
Clipboard.getData(Clipboard.kTextPlain).then((final clipboardContent) { |
||||||
|
if (clipboardContent != null && clipboardContent.text != null) { |
||||||
|
final uri = Uri.tryParse(clipboardContent.text!); |
||||||
|
if (uri != null && (uri.scheme == 'http' || uri.scheme == 'https')) { |
||||||
|
controller.text = clipboardContent.text!; |
||||||
|
} |
||||||
|
} |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
controller.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => ResultBuilder<List<news.Folder>>.behaviorSubject( |
||||||
|
subject: widget.bloc.folders, |
||||||
|
builder: (final context, final folders) => NeonDialog( |
||||||
|
title: Text(NewsLocalizations.of(context).feedAdd), |
||||||
|
children: [ |
||||||
|
Form( |
||||||
|
key: formKey, |
||||||
|
child: Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
TextFormField( |
||||||
|
autofocus: true, |
||||||
|
controller: controller, |
||||||
|
decoration: const InputDecoration( |
||||||
|
hintText: 'https://...', |
||||||
|
), |
||||||
|
keyboardType: TextInputType.url, |
||||||
|
validator: (final input) => validateHttpUrl(context, input), |
||||||
|
onFieldSubmitted: (final _) { |
||||||
|
submit(); |
||||||
|
}, |
||||||
|
), |
||||||
|
if (widget.folderID == null) ...[ |
||||||
|
Center( |
||||||
|
child: NeonError( |
||||||
|
folders.error, |
||||||
|
onRetry: widget.bloc.refresh, |
||||||
|
), |
||||||
|
), |
||||||
|
Center( |
||||||
|
child: NeonLinearProgressIndicator( |
||||||
|
visible: folders.isLoading, |
||||||
|
), |
||||||
|
), |
||||||
|
if (folders.hasData) ...[ |
||||||
|
NewsFolderSelect( |
||||||
|
folders: folders.requireData, |
||||||
|
value: folder, |
||||||
|
onChanged: (final f) { |
||||||
|
setState(() { |
||||||
|
folder = f; |
||||||
|
}); |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
], |
||||||
|
ElevatedButton( |
||||||
|
onPressed: folders.hasData ? submit : null, |
||||||
|
child: Text(NewsLocalizations.of(context).feedAdd), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
class NewsCreateFolderDialog extends StatefulWidget { |
||||||
|
const NewsCreateFolderDialog({ |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
@override |
||||||
|
State<NewsCreateFolderDialog> createState() => _NewsCreateFolderDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _NewsCreateFolderDialogState extends State<NewsCreateFolderDialog> { |
||||||
|
final formKey = GlobalKey<FormState>(); |
||||||
|
|
||||||
|
final controller = TextEditingController(); |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
controller.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
void submit() { |
||||||
|
if (formKey.currentState!.validate()) { |
||||||
|
Navigator.of(context).pop(controller.text); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => NeonDialog( |
||||||
|
title: Text(NewsLocalizations.of(context).folderCreate), |
||||||
|
children: [ |
||||||
|
Form( |
||||||
|
key: formKey, |
||||||
|
child: Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
TextFormField( |
||||||
|
autofocus: true, |
||||||
|
controller: controller, |
||||||
|
decoration: InputDecoration( |
||||||
|
hintText: NewsLocalizations.of(context).folderCreateName, |
||||||
|
), |
||||||
|
validator: (final input) => validateNotEmpty(context, input), |
||||||
|
onFieldSubmitted: (final _) { |
||||||
|
submit(); |
||||||
|
}, |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: submit, |
||||||
|
child: Text(NewsLocalizations.of(context).folderCreate), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
class NewsFeedShowURLDialog extends StatefulWidget { |
||||||
|
const NewsFeedShowURLDialog({ |
||||||
|
required this.feed, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final news.Feed feed; |
||||||
|
|
||||||
|
@override |
||||||
|
State<NewsFeedShowURLDialog> createState() => _NewsFeedShowURLDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _NewsFeedShowURLDialogState extends State<NewsFeedShowURLDialog> { |
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => AlertDialog( |
||||||
|
title: Text(widget.feed.url), |
||||||
|
actions: [ |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () async { |
||||||
|
await Clipboard.setData( |
||||||
|
ClipboardData( |
||||||
|
text: widget.feed.url, |
||||||
|
), |
||||||
|
); |
||||||
|
if (mounted) { |
||||||
|
ScaffoldMessenger.of(context).showSnackBar( |
||||||
|
SnackBar( |
||||||
|
content: Text(NewsLocalizations.of(context).feedCopiedURL), |
||||||
|
), |
||||||
|
); |
||||||
|
Navigator.of(context).pop(); |
||||||
|
} |
||||||
|
}, |
||||||
|
child: Text(NewsLocalizations.of(context).feedCopyURL), |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () { |
||||||
|
Navigator.of(context).pop(); |
||||||
|
}, |
||||||
|
child: Text(NewsLocalizations.of(context).actionClose), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
class NewsFeedUpdateErrorDialog extends StatefulWidget { |
||||||
|
const NewsFeedUpdateErrorDialog({ |
||||||
|
required this.feed, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final news.Feed feed; |
||||||
|
|
||||||
|
@override |
||||||
|
State<NewsFeedUpdateErrorDialog> createState() => _NewsFeedUpdateErrorDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _NewsFeedUpdateErrorDialogState extends State<NewsFeedUpdateErrorDialog> { |
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => AlertDialog( |
||||||
|
title: Text(widget.feed.lastUpdateError!), |
||||||
|
actions: [ |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () async { |
||||||
|
await Clipboard.setData( |
||||||
|
ClipboardData( |
||||||
|
text: widget.feed.lastUpdateError!, |
||||||
|
), |
||||||
|
); |
||||||
|
if (mounted) { |
||||||
|
ScaffoldMessenger.of(context).showSnackBar( |
||||||
|
SnackBar( |
||||||
|
content: Text(NewsLocalizations.of(context).feedCopiedErrorMessage), |
||||||
|
), |
||||||
|
); |
||||||
|
Navigator.of(context).pop(); |
||||||
|
} |
||||||
|
}, |
||||||
|
child: Text(NewsLocalizations.of(context).feedCopyErrorMessage), |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () { |
||||||
|
Navigator.of(context).pop(); |
||||||
|
}, |
||||||
|
child: Text(NewsLocalizations.of(context).actionClose), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
class NewsMoveFeedDialog extends StatefulWidget { |
||||||
|
const NewsMoveFeedDialog({ |
||||||
|
required this.folders, |
||||||
|
required this.feed, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final List<news.Folder> folders; |
||||||
|
final news.Feed feed; |
||||||
|
|
||||||
|
@override |
||||||
|
State<NewsMoveFeedDialog> createState() => _NewsMoveFeedDialogState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _NewsMoveFeedDialogState extends State<NewsMoveFeedDialog> { |
||||||
|
final formKey = GlobalKey<FormState>(); |
||||||
|
|
||||||
|
news.Folder? folder; |
||||||
|
|
||||||
|
void submit() { |
||||||
|
if (formKey.currentState!.validate()) { |
||||||
|
Navigator.of(context).pop([folder?.id]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => NeonDialog( |
||||||
|
title: Text(NewsLocalizations.of(context).feedMove), |
||||||
|
children: [ |
||||||
|
Form( |
||||||
|
key: formKey, |
||||||
|
child: Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
NewsFolderSelect( |
||||||
|
folders: widget.folders, |
||||||
|
value: widget.feed.folderId != null |
||||||
|
? widget.folders.singleWhere((final folder) => folder.id == widget.feed.folderId) |
||||||
|
: null, |
||||||
|
onChanged: (final f) { |
||||||
|
setState(() { |
||||||
|
folder = f; |
||||||
|
}); |
||||||
|
}, |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: submit, |
||||||
|
child: Text(NewsLocalizations.of(context).feedMove), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
@ -1,70 +0,0 @@ |
|||||||
part of '../neon_notes.dart'; |
|
||||||
|
|
||||||
class NotesSelectCategoryDialog extends StatefulWidget { |
|
||||||
const NotesSelectCategoryDialog({ |
|
||||||
required this.bloc, |
|
||||||
this.initialCategory, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final NotesBloc bloc; |
|
||||||
final String? initialCategory; |
|
||||||
|
|
||||||
@override |
|
||||||
State<NotesSelectCategoryDialog> createState() => _NotesSelectCategoryDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> { |
|
||||||
final formKey = GlobalKey<FormState>(); |
|
||||||
|
|
||||||
String? selectedCategory; |
|
||||||
|
|
||||||
void submit() { |
|
||||||
if (formKey.currentState!.validate()) { |
|
||||||
Navigator.of(context).pop(selectedCategory); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(final BuildContext context) => ResultBuilder<List<notes.Note>>.behaviorSubject( |
|
||||||
subject: widget.bloc.notesList, |
|
||||||
builder: (final context, final notes) => NeonDialog( |
|
||||||
title: Text(NotesLocalizations.of(context).category), |
|
||||||
children: [ |
|
||||||
Form( |
|
||||||
key: formKey, |
|
||||||
child: Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.end, |
|
||||||
children: [ |
|
||||||
Center( |
|
||||||
child: NeonError( |
|
||||||
notes.error, |
|
||||||
onRetry: widget.bloc.refresh, |
|
||||||
), |
|
||||||
), |
|
||||||
Center( |
|
||||||
child: NeonLinearProgressIndicator( |
|
||||||
visible: notes.isLoading, |
|
||||||
), |
|
||||||
), |
|
||||||
if (notes.hasData) ...[ |
|
||||||
NotesCategorySelect( |
|
||||||
categories: notes.requireData.map((final note) => note.category).toSet().toList(), |
|
||||||
initialValue: widget.initialCategory, |
|
||||||
onChanged: (final category) { |
|
||||||
selectedCategory = category; |
|
||||||
}, |
|
||||||
onSubmitted: submit, |
|
||||||
), |
|
||||||
], |
|
||||||
ElevatedButton( |
|
||||||
onPressed: submit, |
|
||||||
child: Text(NotesLocalizations.of(context).noteSetCategory), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
Loading…
Reference in new issue