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/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/hex_color.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/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