mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Cover plugin widget breakdown 1928 (#2007)
* feat: added emoji and network image support * fix: code cleanup and improvements * fix: blank preview on invalid image save * fix: flutter analyzer warnings * fix: code refactor and bug fixes * chore: removed unused imports * chore: formate code * chore: widget tree breakdown * chore: added the deleted code --------- Co-authored-by: ahmeduzair890 <ahmeduzair12123@gmail.com> Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_image_picker_bloc.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_image_picker_bloc.dart';
|
||||||
@ -25,6 +26,84 @@ class CoverImagePicker extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CoverImagePickerState extends State<CoverImagePicker> {
|
class _CoverImagePickerState extends State<CoverImagePicker> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => CoverImagePickerBloc()
|
||||||
|
..add(const CoverImagePickerEvent.initialEvent()),
|
||||||
|
child: BlocListener<CoverImagePickerBloc, CoverImagePickerState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is NetworkImagePicked) {
|
||||||
|
state.successOrFail.isRight()
|
||||||
|
? showSnapBar(context,
|
||||||
|
LocaleKeys.document_plugins_cover_invalidImageUrl.tr())
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
if (state is Done) {
|
||||||
|
state.successOrFail.fold(
|
||||||
|
(l) => widget.onFileSubmit(l),
|
||||||
|
(r) => showSnapBar(
|
||||||
|
context,
|
||||||
|
LocaleKeys.document_plugins_cover_failedToAddImageToGallery
|
||||||
|
.tr()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: BlocBuilder<CoverImagePickerBloc, CoverImagePickerState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
state is Loading
|
||||||
|
? const SizedBox(
|
||||||
|
height: 180,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: CoverImagePreviewWidget(state: state),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
NetworkImageUrlInput(
|
||||||
|
onAdd: (url) {
|
||||||
|
context.read<CoverImagePickerBloc>().add(UrlSubmit(url));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
ImagePickerActionButtons(
|
||||||
|
onBackPressed: () {
|
||||||
|
widget.onBackPressed();
|
||||||
|
},
|
||||||
|
onSave: () {
|
||||||
|
context.read<CoverImagePickerBloc>().add(
|
||||||
|
SaveToGallery(state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkImageUrlInput extends StatefulWidget {
|
||||||
|
final void Function(String color) onAdd;
|
||||||
|
|
||||||
|
const NetworkImageUrlInput({
|
||||||
|
super.key,
|
||||||
|
required this.onAdd,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NetworkImageUrlInput> createState() => _NetworkImageUrlInputState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NetworkImageUrlInputState extends State<NetworkImageUrlInput> {
|
||||||
TextEditingController urlController = TextEditingController();
|
TextEditingController urlController = TextEditingController();
|
||||||
bool get buttonDisabled => urlController.text.isEmpty;
|
bool get buttonDisabled => urlController.text.isEmpty;
|
||||||
|
|
||||||
@ -36,6 +115,85 @@ class _CoverImagePickerState extends State<CoverImagePicker> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: FlowyTextField(
|
||||||
|
controller: urlController,
|
||||||
|
hintText: LocaleKeys.document_plugins_cover_enterImageUrl.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 5,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: RoundedTextButton(
|
||||||
|
onPressed: () {
|
||||||
|
urlController.text.isNotEmpty
|
||||||
|
? widget.onAdd(urlController.text)
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
fillColor: buttonDisabled
|
||||||
|
? Colors.grey
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
height: 36,
|
||||||
|
title: LocaleKeys.document_plugins_cover_add.tr(),
|
||||||
|
borderRadius: Corners.s8Border,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImagePickerActionButtons extends StatelessWidget {
|
||||||
|
final VoidCallback onBackPressed;
|
||||||
|
final VoidCallback onSave;
|
||||||
|
|
||||||
|
const ImagePickerActionButtons(
|
||||||
|
{super.key, required this.onBackPressed, required this.onSave});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FlowyTextButton(
|
||||||
|
LocaleKeys.document_plugins_cover_back.tr(),
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
onPressed: () => onBackPressed(),
|
||||||
|
),
|
||||||
|
FlowyTextButton(
|
||||||
|
LocaleKeys.document_plugins_cover_saveToGallery.tr(),
|
||||||
|
onPressed: () => onSave(),
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
fontColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoverImagePreviewWidget extends StatefulWidget {
|
||||||
|
final dynamic state;
|
||||||
|
|
||||||
|
const CoverImagePreviewWidget({super.key, required this.state});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CoverImagePreviewWidget> createState() =>
|
||||||
|
_CoverImagePreviewWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CoverImagePreviewWidgetState extends State<CoverImagePreviewWidget> {
|
||||||
_buildFilePickerWidget(BuildContext ctx) {
|
_buildFilePickerWidget(BuildContext ctx) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -105,150 +263,43 @@ class _CoverImagePickerState extends State<CoverImagePicker> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return Stack(
|
||||||
create: (context) => CoverImagePickerBloc()
|
children: [
|
||||||
..add(const CoverImagePickerEvent.initialEvent()),
|
Container(
|
||||||
child: BlocListener<CoverImagePickerBloc, CoverImagePickerState>(
|
height: 180,
|
||||||
listener: (context, state) {
|
alignment: Alignment.center,
|
||||||
if (state is NetworkImagePicked) {
|
decoration: BoxDecoration(
|
||||||
state.successOrFail.isRight()
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
? showSnapBar(context,
|
borderRadius: Corners.s6Border,
|
||||||
LocaleKeys.document_plugins_cover_invalidImageUrl.tr())
|
image: widget.state is Initial
|
||||||
: null;
|
? null
|
||||||
}
|
: widget.state is NetworkImagePicked
|
||||||
if (state is Done) {
|
? widget.state.successOrFail.fold(
|
||||||
state.successOrFail.fold(
|
(path) => DecorationImage(
|
||||||
(l) => widget.onFileSubmit(l),
|
image: NetworkImage(path), fit: BoxFit.cover),
|
||||||
(r) => showSnapBar(
|
(r) => null)
|
||||||
context,
|
: widget.state is FileImagePicked
|
||||||
LocaleKeys.document_plugins_cover_failedToAddImageToGallery
|
? DecorationImage(
|
||||||
.tr()));
|
image: FileImage(File(widget.state.path)),
|
||||||
}
|
fit: BoxFit.cover)
|
||||||
},
|
: null),
|
||||||
child: BlocBuilder<CoverImagePickerBloc, CoverImagePickerState>(
|
child: (widget.state is Initial)
|
||||||
builder: (context, state) {
|
? _buildFilePickerWidget(context)
|
||||||
return Column(
|
: (widget.state is NetworkImagePicked)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? widget.state.successOrFail.fold(
|
||||||
children: [
|
(l) => null,
|
||||||
state is Loading
|
(r) => _buildFilePickerWidget(
|
||||||
? const SizedBox(
|
context,
|
||||||
height: 180,
|
|
||||||
child: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Stack(
|
: null),
|
||||||
children: [
|
(widget.state is FileImagePicked)
|
||||||
Container(
|
? _buildImageDeleteButton(context)
|
||||||
height: 180,
|
: (widget.state is NetworkImagePicked)
|
||||||
alignment: Alignment.center,
|
? widget.state.successOrFail.fold(
|
||||||
decoration: BoxDecoration(
|
(l) => _buildImageDeleteButton(context), (r) => Container())
|
||||||
color:
|
: Container()
|
||||||
Theme.of(context).colorScheme.secondary,
|
],
|
||||||
borderRadius: Corners.s6Border,
|
|
||||||
image: state is Initial
|
|
||||||
? null
|
|
||||||
: state is NetworkImagePicked
|
|
||||||
? state.successOrFail.fold(
|
|
||||||
(path) => DecorationImage(
|
|
||||||
image: NetworkImage(path),
|
|
||||||
fit: BoxFit.cover),
|
|
||||||
(r) => null)
|
|
||||||
: state is FileImagePicked
|
|
||||||
? DecorationImage(
|
|
||||||
image: FileImage(
|
|
||||||
File(state.path)),
|
|
||||||
fit: BoxFit.cover)
|
|
||||||
: null),
|
|
||||||
child: (state is Initial)
|
|
||||||
? _buildFilePickerWidget(context)
|
|
||||||
: (state is NetworkImagePicked)
|
|
||||||
? state.successOrFail.fold(
|
|
||||||
(l) => null,
|
|
||||||
(r) => _buildFilePickerWidget(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null),
|
|
||||||
(state is FileImagePicked)
|
|
||||||
? _buildImageDeleteButton(context)
|
|
||||||
: (state is NetworkImagePicked)
|
|
||||||
? state.successOrFail.fold(
|
|
||||||
(l) => _buildImageDeleteButton(context),
|
|
||||||
(r) => Container())
|
|
||||||
: Container()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: FlowyTextField(
|
|
||||||
controller: urlController,
|
|
||||||
hintText: LocaleKeys
|
|
||||||
.document_plugins_cover_enterImageUrl
|
|
||||||
.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 5,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: RoundedTextButton(
|
|
||||||
onPressed: () {
|
|
||||||
urlController.text.isNotEmpty
|
|
||||||
? context
|
|
||||||
.read<CoverImagePickerBloc>()
|
|
||||||
.add(UrlSubmit(urlController.text))
|
|
||||||
: null;
|
|
||||||
},
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
fillColor: buttonDisabled
|
|
||||||
? Colors.grey
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
height: 36,
|
|
||||||
title: LocaleKeys.document_plugins_cover_add.tr(),
|
|
||||||
borderRadius: Corners.s8Border,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
FlowyTextButton(
|
|
||||||
LocaleKeys.document_plugins_cover_back.tr(),
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
fillColor: Colors.transparent,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
onPressed: () => widget.onBackPressed(),
|
|
||||||
),
|
|
||||||
FlowyTextButton(
|
|
||||||
LocaleKeys.document_plugins_cover_saveToGallery.tr(),
|
|
||||||
onPressed: () async {
|
|
||||||
context
|
|
||||||
.read<CoverImagePickerBloc>()
|
|
||||||
.add(SaveToGallery(state));
|
|
||||||
},
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
fillColor: Colors.transparent,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
fontColor: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user