mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: select cover image on upload (#3488)
This commit is contained in:
@ -41,36 +41,6 @@ class ChangeCoverPopover extends StatefulWidget {
|
|||||||
State<ChangeCoverPopover> createState() => _ChangeCoverPopoverState();
|
State<ChangeCoverPopover> createState() => _ChangeCoverPopoverState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorOption {
|
|
||||||
final String colorHex;
|
|
||||||
|
|
||||||
final String name;
|
|
||||||
const ColorOption({
|
|
||||||
required this.colorHex,
|
|
||||||
required this.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoverColorPicker extends StatefulWidget {
|
|
||||||
final String? selectedBackgroundColorHex;
|
|
||||||
|
|
||||||
final Color pickerBackgroundColor;
|
|
||||||
final Color pickerItemHoverColor;
|
|
||||||
final void Function(String color) onSubmittedBackgroundColorHex;
|
|
||||||
final List<ColorOption> backgroundColorOptions;
|
|
||||||
const CoverColorPicker({
|
|
||||||
super.key,
|
|
||||||
this.selectedBackgroundColorHex,
|
|
||||||
required this.pickerBackgroundColor,
|
|
||||||
required this.backgroundColorOptions,
|
|
||||||
required this.pickerItemHoverColor,
|
|
||||||
required this.onSubmittedBackgroundColorHex,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CoverColorPicker> createState() => _CoverColorPickerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
||||||
bool isAddingImage = false;
|
bool isAddingImage = false;
|
||||||
|
|
||||||
@ -81,7 +51,15 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
|||||||
editorState: widget.editorState,
|
editorState: widget.editorState,
|
||||||
node: widget.node,
|
node: widget.node,
|
||||||
)..add(const ChangeCoverPopoverEvent.fetchPickedImagePaths()),
|
)..add(const ChangeCoverPopoverEvent.fetchPickedImagePaths()),
|
||||||
child: BlocBuilder<ChangeCoverPopoverBloc, ChangeCoverPopoverState>(
|
child: BlocConsumer<ChangeCoverPopoverBloc, ChangeCoverPopoverState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is Loaded && state.selectLatestImage) {
|
||||||
|
widget.onCoverChanged(
|
||||||
|
CoverType.file,
|
||||||
|
state.imageNames.last,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
@ -91,14 +69,15 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
|||||||
onBackPressed: () => setState(() {
|
onBackPressed: () => setState(() {
|
||||||
isAddingImage = false;
|
isAddingImage = false;
|
||||||
}),
|
}),
|
||||||
onFileSubmit: (List<String> path) {
|
onFileSubmit: (_) {
|
||||||
context.read<ChangeCoverPopoverBloc>().add(
|
context.read<ChangeCoverPopoverBloc>().add(
|
||||||
const ChangeCoverPopoverEvent
|
const ChangeCoverPopoverEvent
|
||||||
.fetchPickedImagePaths(),
|
.fetchPickedImagePaths(
|
||||||
|
selectLatestImage: true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
setState(() {
|
|
||||||
isAddingImage = false;
|
setState(() => isAddingImage = false);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: _buildCoverSelection(),
|
: _buildCoverSelection(),
|
||||||
@ -294,7 +273,8 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container();
|
|
||||||
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -314,6 +294,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class NewCustomCoverButton extends StatelessWidget {
|
class NewCustomCoverButton extends StatelessWidget {
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
const NewCustomCoverButton({super.key, required this.onPressed});
|
const NewCustomCoverButton({super.key, required this.onPressed});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -337,6 +318,85 @@ class NewCustomCoverButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ColorOption {
|
||||||
|
final String colorHex;
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
const ColorOption({
|
||||||
|
required this.colorHex,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoverColorPicker extends StatefulWidget {
|
||||||
|
final String? selectedBackgroundColorHex;
|
||||||
|
|
||||||
|
final Color pickerBackgroundColor;
|
||||||
|
final Color pickerItemHoverColor;
|
||||||
|
final void Function(String color) onSubmittedBackgroundColorHex;
|
||||||
|
final List<ColorOption> backgroundColorOptions;
|
||||||
|
const CoverColorPicker({
|
||||||
|
super.key,
|
||||||
|
this.selectedBackgroundColorHex,
|
||||||
|
required this.pickerBackgroundColor,
|
||||||
|
required this.backgroundColorOptions,
|
||||||
|
required this.pickerItemHoverColor,
|
||||||
|
required this.onSubmittedBackgroundColorHex,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CoverColorPicker> createState() => _CoverColorPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CoverColorPickerState extends State<CoverColorPicker> {
|
||||||
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 30,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ScrollConfiguration(
|
||||||
|
behavior: ScrollConfiguration.of(context).copyWith(
|
||||||
|
dragDevices: {
|
||||||
|
PointerDeviceKind.touch,
|
||||||
|
PointerDeviceKind.mouse,
|
||||||
|
},
|
||||||
|
platform: TargetPlatform.windows,
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: _buildColorItems(
|
||||||
|
widget.backgroundColorOptions,
|
||||||
|
widget.selectedBackgroundColorHex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
scrollController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildColorItems(List<ColorOption> options, String? selectedColor) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: options
|
||||||
|
.map(
|
||||||
|
(e) => ColorItem(
|
||||||
|
option: e,
|
||||||
|
isChecked: e.colorHex == selectedColor,
|
||||||
|
hoverColor: widget.pickerItemHoverColor,
|
||||||
|
onTap: widget.onSubmittedBackgroundColorHex,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DeleteImageAlertDialog extends StatelessWidget {
|
class DeleteImageAlertDialog extends StatelessWidget {
|
||||||
const DeleteImageAlertDialog({
|
const DeleteImageAlertDialog({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -458,55 +518,6 @@ class _ImageGridItemState extends State<ImageGridItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CoverColorPickerState extends State<CoverColorPicker> {
|
|
||||||
final scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
height: 30,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: ScrollConfiguration(
|
|
||||||
behavior: ScrollConfiguration.of(context).copyWith(
|
|
||||||
dragDevices: {
|
|
||||||
PointerDeviceKind.touch,
|
|
||||||
PointerDeviceKind.mouse,
|
|
||||||
},
|
|
||||||
platform: TargetPlatform.windows,
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: _buildColorItems(
|
|
||||||
widget.backgroundColorOptions,
|
|
||||||
widget.selectedBackgroundColorHex,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
scrollController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildColorItems(List<ColorOption> options, String? selectedColor) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: options
|
|
||||||
.map(
|
|
||||||
(e) => ColorItem(
|
|
||||||
option: e,
|
|
||||||
isChecked: e.colorHex == selectedColor,
|
|
||||||
hoverColor: widget.pickerItemHoverColor,
|
|
||||||
onTap: widget.onSubmittedBackgroundColorHex,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class ColorItem extends StatelessWidget {
|
class ColorItem extends StatelessWidget {
|
||||||
final ColorOption option;
|
final ColorOption option;
|
||||||
|
@ -16,18 +16,28 @@ class ChangeCoverPopoverBloc
|
|||||||
final Node node;
|
final Node node;
|
||||||
late final SharedPreferences _prefs;
|
late final SharedPreferences _prefs;
|
||||||
final _initCompleter = Completer<void>();
|
final _initCompleter = Completer<void>();
|
||||||
ChangeCoverPopoverBloc({required this.editorState, required this.node})
|
|
||||||
: super(const ChangeCoverPopoverState.initial()) {
|
ChangeCoverPopoverBloc({
|
||||||
|
required this.editorState,
|
||||||
|
required this.node,
|
||||||
|
}) : super(const ChangeCoverPopoverState.initial()) {
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
_prefs = prefs;
|
_prefs = prefs;
|
||||||
_initCompleter.complete();
|
_initCompleter.complete();
|
||||||
});
|
});
|
||||||
|
|
||||||
on<ChangeCoverPopoverEvent>((event, emit) async {
|
on<ChangeCoverPopoverEvent>((event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
fetchPickedImagePaths:
|
fetchPickedImagePaths:
|
||||||
(FetchPickedImagePaths fetchPickedImagePaths) async {
|
(FetchPickedImagePaths fetchPickedImagePaths) async {
|
||||||
final imageNames = await _getPreviouslyPickedImagePaths();
|
final imageNames = await _getPreviouslyPickedImagePaths();
|
||||||
emit(ChangeCoverPopoverState.loaded(imageNames));
|
|
||||||
|
emit(
|
||||||
|
ChangeCoverPopoverState.loaded(
|
||||||
|
imageNames,
|
||||||
|
selectLatestImage: fetchPickedImagePaths.selectLatestImage,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
deleteImage: (DeleteImage deleteImage) async {
|
deleteImage: (DeleteImage deleteImage) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
@ -100,8 +110,9 @@ class ChangeCoverPopoverBloc
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class ChangeCoverPopoverEvent with _$ChangeCoverPopoverEvent {
|
class ChangeCoverPopoverEvent with _$ChangeCoverPopoverEvent {
|
||||||
const factory ChangeCoverPopoverEvent.fetchPickedImagePaths() =
|
const factory ChangeCoverPopoverEvent.fetchPickedImagePaths({
|
||||||
FetchPickedImagePaths;
|
@Default(false) bool selectLatestImage,
|
||||||
|
}) = FetchPickedImagePaths;
|
||||||
|
|
||||||
const factory ChangeCoverPopoverEvent.deleteImage(String path) = DeleteImage;
|
const factory ChangeCoverPopoverEvent.deleteImage(String path) = DeleteImage;
|
||||||
const factory ChangeCoverPopoverEvent.clearAllImages() = ClearAllImages;
|
const factory ChangeCoverPopoverEvent.clearAllImages() = ClearAllImages;
|
||||||
@ -112,6 +123,7 @@ class ChangeCoverPopoverState with _$ChangeCoverPopoverState {
|
|||||||
const factory ChangeCoverPopoverState.initial() = Initial;
|
const factory ChangeCoverPopoverState.initial() = Initial;
|
||||||
const factory ChangeCoverPopoverState.loading() = Loading;
|
const factory ChangeCoverPopoverState.loading() = Loading;
|
||||||
const factory ChangeCoverPopoverState.loaded(
|
const factory ChangeCoverPopoverState.loaded(
|
||||||
List<String> imageNames,
|
List<String> imageNames, {
|
||||||
) = Loaded;
|
@Default(false) selectLatestImage,
|
||||||
|
}) = Loaded;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user