Refactor: extract color tools from main editor

This commit is contained in:
Ming Ming 2022-09-05 18:04:08 +08:00
parent 50849817a6
commit 839ef8bb03
3 changed files with 537 additions and 443 deletions

View file

@ -6,17 +6,15 @@ import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/double_extension.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/help_utils.dart' as help_util;
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/pixel_image_provider.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/url_launcher_util.dart';
import 'package:nc_photos/widget/handler/permission_handler.dart';
import 'package:nc_photos/widget/stateful_slider.dart';
import 'package:nc_photos/widget/image_editor/color_toolbar.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
class ImageEditorArguments {
@ -122,8 +120,13 @@ class _ImageEditorState extends State<ImageEditor> {
)
: Container(),
),
_buildFilterOption(context),
_buildFilterBar(context),
ColorToolbar(
initialState: _colorFilters,
onActiveFiltersChanged: (colorFilters) {
_colorFilters = colorFilters.toList();
_applyFilters();
},
),
],
),
),
@ -137,7 +140,7 @@ class _ImageEditorState extends State<ImageEditor> {
leading: BackButton(onPressed: () => _onBackButton(context)),
title: Text(L10n.global().imageEditTitle),
actions: [
if (_filters.isNotEmpty)
if (_isModified)
IconButton(
icon: const Icon(Icons.save_outlined),
tooltip: L10n.global().saveTooltip,
@ -153,268 +156,8 @@ class _ImageEditorState extends State<ImageEditor> {
],
);
Widget _buildFilterBar(BuildContext context) {
return Align(
alignment: AlignmentDirectional.centerStart,
child: Material(
type: MaterialType.transparency,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
const SizedBox(width: 16),
_FilterButton(
icon: Icons.brightness_medium,
label: L10n.global().imageEditColorBrightness,
onPressed: _onBrightnessPressed,
isSelected: _selectedFilter == _ColorFilterType.brightness,
activationOrder:
_filters.keys.indexOf(_ColorFilterType.brightness),
),
_FilterButton(
icon: Icons.contrast,
label: L10n.global().imageEditColorContrast,
onPressed: _onContrastPressed,
isSelected: _selectedFilter == _ColorFilterType.contrast,
activationOrder:
_filters.keys.indexOf(_ColorFilterType.contrast),
),
_FilterButton(
icon: Icons.circle,
label: L10n.global().imageEditColorWhitePoint,
onPressed: _onWhitePointPressed,
isSelected: _selectedFilter == _ColorFilterType.whitePoint,
activationOrder:
_filters.keys.indexOf(_ColorFilterType.whitePoint),
),
_FilterButton(
icon: Icons.circle_outlined,
label: L10n.global().imageEditColorBlackPoint,
onPressed: _onBlackPointPressed,
isSelected: _selectedFilter == _ColorFilterType.blackPoint,
activationOrder:
_filters.keys.indexOf(_ColorFilterType.blackPoint),
),
_FilterButton(
icon: Icons.invert_colors,
label: L10n.global().imageEditColorSaturation,
onPressed: _onSaturationPressed,
isSelected: _selectedFilter == _ColorFilterType.saturation,
activationOrder:
_filters.keys.indexOf(_ColorFilterType.saturation),
),
_FilterButton(
icon: Icons.thermostat,
label: L10n.global().imageEditColorWarmth,
onPressed: _onWarmthPressed,
isSelected: _selectedFilter == _ColorFilterType.warmth,
activationOrder: _filters.keys.indexOf(_ColorFilterType.warmth),
),
_FilterButton(
icon: Icons.colorize,
label: L10n.global().imageEditColorTint,
onPressed: _onTintPressed,
isSelected: _selectedFilter == _ColorFilterType.tint,
activationOrder: _filters.keys.indexOf(_ColorFilterType.tint),
),
const SizedBox(width: 16),
],
),
),
),
);
}
Widget _buildFilterOption(BuildContext context) {
Widget? child;
switch (_selectedFilter) {
case _ColorFilterType.brightness:
child = _buildBrightnessOption(context);
break;
case _ColorFilterType.contrast:
child = _buildContrastOption(context);
break;
case _ColorFilterType.whitePoint:
child = _buildWhitePointOption(context);
break;
case _ColorFilterType.blackPoint:
child = _buildBlackPointOption(context);
break;
case _ColorFilterType.saturation:
child = _buildSaturationOption(context);
break;
case _ColorFilterType.warmth:
child = _buildWarmthOption(context);
break;
case _ColorFilterType.tint:
child = _buildTintOption(context);
break;
case null:
child = null;
break;
}
return Container(
height: 96,
alignment: Alignment.center,
child: child,
);
}
Widget _buildSliderOption(
BuildContext context, {
required Key key,
required double min,
required double max,
required double initialValue,
ValueChanged<double>? onChangeEnd,
}) {
return AppTheme.dark(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Stack(
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(min.toStringAsFixedTruncated(1)),
),
if (min < 0 && max > 0)
const Align(
alignment: AlignmentDirectional.center,
child: Text("0"),
),
Align(
alignment: AlignmentDirectional.centerEnd,
child: Text(max.toStringAsFixedTruncated(1)),
),
],
),
),
StatefulSlider(
key: key,
initialValue: initialValue.toDouble(),
min: min.toDouble(),
max: max.toDouble(),
onChangeEnd: onChangeEnd,
),
],
),
),
);
}
Widget _buildBrightnessOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.brightness.name),
min: -100,
max: 100,
initialValue:
(_filters[_ColorFilterType.brightness] as _BrightnessArguments)
.value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.brightness] as _BrightnessArguments)
.value = value;
_applyFilters();
},
);
Widget _buildContrastOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.contrast.name),
min: -100,
max: 100,
initialValue:
(_filters[_ColorFilterType.contrast] as _ContrastArguments).value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.contrast] as _ContrastArguments).value =
value;
_applyFilters();
},
);
Widget _buildWhitePointOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.whitePoint.name),
min: -100,
max: 100,
initialValue:
(_filters[_ColorFilterType.whitePoint] as _WhitePointArguments)
.value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.whitePoint] as _WhitePointArguments)
.value = value;
_applyFilters();
},
);
Widget _buildBlackPointOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.blackPoint.name),
min: -100,
max: 100,
initialValue:
(_filters[_ColorFilterType.blackPoint] as _BlackPointArguments)
.value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.blackPoint] as _BlackPointArguments)
.value = value;
_applyFilters();
},
);
Widget _buildSaturationOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.saturation.name),
min: -100,
max: 100,
initialValue:
(_filters[_ColorFilterType.saturation] as _SaturationArguments)
.value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.saturation] as _SaturationArguments)
.value = value;
_applyFilters();
},
);
Widget _buildWarmthOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.warmth.name),
min: -100,
max: 100,
initialValue:
(_filters[_ColorFilterType.warmth] as _WarmthArguments).value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.warmth] as _WarmthArguments).value = value;
_applyFilters();
},
);
Widget _buildTintOption(BuildContext context) => _buildSliderOption(
context,
key: Key(_ColorFilterType.tint.name),
min: -100,
max: 100,
initialValue: (_filters[_ColorFilterType.tint] as _TintArguments).value,
onChangeEnd: (value) {
(_filters[_ColorFilterType.tint] as _TintArguments).value = value;
_applyFilters();
},
);
Future<void> _onBackButton(BuildContext context) async {
if (_filters.isEmpty) {
if (!_isModified) {
Navigator.of(context).pop();
return;
}
@ -459,69 +202,8 @@ class _ImageEditorState extends State<ImageEditor> {
Navigator.of(context).pop();
}
void _onFilterPressed(_ColorFilterType type, _FilterArguments defArgs) {
if (_selectedFilter == type) {
// deactivate filter
setState(() {
_selectedFilter = null;
_filters.remove(type);
});
} else {
setState(() {
_selectedFilter = type;
_filters[type] ??= defArgs;
});
}
_applyFilters();
}
void _onBrightnessPressed() =>
_onFilterPressed(_ColorFilterType.brightness, _BrightnessArguments(0));
void _onContrastPressed() =>
_onFilterPressed(_ColorFilterType.contrast, _ContrastArguments(0));
void _onWhitePointPressed() =>
_onFilterPressed(_ColorFilterType.whitePoint, _WhitePointArguments(0));
void _onBlackPointPressed() =>
_onFilterPressed(_ColorFilterType.blackPoint, _BlackPointArguments(0));
void _onSaturationPressed() =>
_onFilterPressed(_ColorFilterType.saturation, _SaturationArguments(0));
void _onWarmthPressed() =>
_onFilterPressed(_ColorFilterType.warmth, _WarmthArguments(0));
void _onTintPressed() =>
_onFilterPressed(_ColorFilterType.tint, _TintArguments(0));
List<ImageFilter> _buildFilterList() {
return _filters.entries.map((e) {
switch (e.key) {
case _ColorFilterType.brightness:
return (e.value as _BrightnessArguments)
.run((arg) => ColorBrightnessFilter(arg.value / 100));
case _ColorFilterType.contrast:
return (e.value as _ContrastArguments)
.run((arg) => ColorContrastFilter(arg.value / 100));
case _ColorFilterType.whitePoint:
return (e.value as _WhitePointArguments)
.run((arg) => ColorWhitePointFilter(arg.value / 100));
case _ColorFilterType.blackPoint:
return (e.value as _BlackPointArguments)
.run((arg) => ColorBlackPointFilter(arg.value / 100));
case _ColorFilterType.saturation:
return (e.value as _SaturationArguments)
.run((arg) => ColorSaturationFilter(arg.value / 100));
case _ColorFilterType.warmth:
return (e.value as _WarmthArguments)
.run((arg) => ColorWarmthFilter(arg.value / 100));
case _ColorFilterType.tint:
return (e.value as _TintArguments)
.run((arg) => ColorTintFilter(arg.value / 100));
}
}).toList();
return _colorFilters.map((f) => f.toImageFilter()).toList();
}
Future<void> _applyFilters() async {
@ -531,121 +213,11 @@ class _ImageEditorState extends State<ImageEditor> {
});
}
bool get _isModified => _colorFilters.isNotEmpty;
bool _isDoneInit = false;
late final Rgba8Image _src;
Rgba8Image? _dst;
final _filters = <_ColorFilterType, _FilterArguments>{};
_ColorFilterType? _selectedFilter;
}
enum _ColorFilterType {
brightness,
contrast,
whitePoint,
blackPoint,
saturation,
warmth,
tint,
}
abstract class _FilterArguments {}
class _FilterDoubleArguments implements _FilterArguments {
_FilterDoubleArguments(this.value);
double value;
}
typedef _BrightnessArguments = _FilterDoubleArguments;
typedef _ContrastArguments = _FilterDoubleArguments;
typedef _WhitePointArguments = _FilterDoubleArguments;
typedef _BlackPointArguments = _FilterDoubleArguments;
typedef _SaturationArguments = _FilterDoubleArguments;
typedef _WarmthArguments = _FilterDoubleArguments;
typedef _TintArguments = _FilterDoubleArguments;
class _FilterButton extends StatelessWidget {
const _FilterButton({
Key? key,
required this.icon,
required this.label,
required this.onPressed,
this.isSelected = false,
this.activationOrder = -1,
}) : super(key: key);
@override
build(BuildContext context) {
final color = !isSelected && isActivated
? AppTheme.primarySwatchDark[900]!.withOpacity(0.4)
: AppTheme.primarySwatchDark[500]!.withOpacity(0.7);
return InkWell(
onTap: onPressed,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 64,
height: 64,
child: Stack(
fit: StackFit.expand,
children: [
AnimatedOpacity(
opacity: isSelected || isActivated ? 1 : 0,
duration: k.animationDurationNormal,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: color,
),
),
),
Center(
child: Icon(
icon,
size: 32,
color: AppTheme.unfocusedIconColorDark,
),
),
if (isActivated)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Align(
alignment: Alignment.topCenter,
child: Text(
(activationOrder + 1).toString(),
style: const TextStyle(
fontSize: 12,
color: AppTheme.unfocusedIconColorDark,
),
),
),
),
],
),
),
const SizedBox(height: 8),
Text(
label,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
color: AppTheme.unfocusedIconColorDark,
),
),
],
),
),
);
}
bool get isActivated => activationOrder >= 0;
final IconData icon;
final String label;
final VoidCallback? onPressed;
final bool isSelected;
final int activationOrder;
var _colorFilters = <ColorArguments>[];
}

View file

@ -0,0 +1,428 @@
import 'package:flutter/material.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/double_extension.dart';
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/widget/image_editor/toolbar_button.dart';
import 'package:nc_photos/widget/stateful_slider.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
enum ColorToolType {
brightness,
contrast,
whitePoint,
blackPoint,
saturation,
warmth,
tint,
}
abstract class ColorArguments {
ImageFilter toImageFilter();
ColorToolType _getToolType();
}
class ColorToolbar extends StatefulWidget {
const ColorToolbar({
Key? key,
required this.initialState,
required this.onActiveFiltersChanged,
}) : super(key: key);
@override
createState() => _ColorToolbarState();
final List<ColorArguments> initialState;
final ValueChanged<Iterable<ColorArguments>> onActiveFiltersChanged;
}
class _ColorToolbarState extends State<ColorToolbar> {
@override
initState() {
super.initState();
for (final s in widget.initialState) {
_filters[s._getToolType()] = s;
}
}
@override
build(BuildContext context) => Column(
children: [
_buildFilterOption(context),
_buildFilterBar(context),
],
);
Widget _buildFilterOption(BuildContext context) {
Widget? child;
switch (_selectedFilter) {
case ColorToolType.brightness:
child = _buildBrightnessOption(context);
break;
case ColorToolType.contrast:
child = _buildContrastOption(context);
break;
case ColorToolType.whitePoint:
child = _buildWhitePointOption(context);
break;
case ColorToolType.blackPoint:
child = _buildBlackPointOption(context);
break;
case ColorToolType.saturation:
child = _buildSaturationOption(context);
break;
case ColorToolType.warmth:
child = _buildWarmthOption(context);
break;
case ColorToolType.tint:
child = _buildTintOption(context);
break;
case null:
child = null;
break;
}
return Container(
height: 80,
alignment: Alignment.bottomCenter,
child: child,
);
}
Widget _buildFilterBar(BuildContext context) {
return Align(
alignment: AlignmentDirectional.centerStart,
child: Material(
type: MaterialType.transparency,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
const SizedBox(width: 16),
ToolbarButton(
icon: Icons.brightness_medium,
label: L10n.global().imageEditColorBrightness,
onPressed: _onBrightnessPressed,
isSelected: _selectedFilter == ColorToolType.brightness,
activationOrder:
_filters.keys.indexOf(ColorToolType.brightness),
),
ToolbarButton(
icon: Icons.contrast,
label: L10n.global().imageEditColorContrast,
onPressed: _onContrastPressed,
isSelected: _selectedFilter == ColorToolType.contrast,
activationOrder: _filters.keys.indexOf(ColorToolType.contrast),
),
ToolbarButton(
icon: Icons.circle,
label: L10n.global().imageEditColorWhitePoint,
onPressed: _onWhitePointPressed,
isSelected: _selectedFilter == ColorToolType.whitePoint,
activationOrder:
_filters.keys.indexOf(ColorToolType.whitePoint),
),
ToolbarButton(
icon: Icons.circle_outlined,
label: L10n.global().imageEditColorBlackPoint,
onPressed: _onBlackPointPressed,
isSelected: _selectedFilter == ColorToolType.blackPoint,
activationOrder:
_filters.keys.indexOf(ColorToolType.blackPoint),
),
ToolbarButton(
icon: Icons.invert_colors,
label: L10n.global().imageEditColorSaturation,
onPressed: _onSaturationPressed,
isSelected: _selectedFilter == ColorToolType.saturation,
activationOrder:
_filters.keys.indexOf(ColorToolType.saturation),
),
ToolbarButton(
icon: Icons.thermostat,
label: L10n.global().imageEditColorWarmth,
onPressed: _onWarmthPressed,
isSelected: _selectedFilter == ColorToolType.warmth,
activationOrder: _filters.keys.indexOf(ColorToolType.warmth),
),
ToolbarButton(
icon: Icons.colorize,
label: L10n.global().imageEditColorTint,
onPressed: _onTintPressed,
isSelected: _selectedFilter == ColorToolType.tint,
activationOrder: _filters.keys.indexOf(ColorToolType.tint),
),
const SizedBox(width: 16),
],
),
),
),
);
}
Widget _buildSliderOption(
BuildContext context, {
required Key key,
required double min,
required double max,
required double initialValue,
ValueChanged<double>? onChangeEnd,
}) {
return AppTheme.dark(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Stack(
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(min.toStringAsFixedTruncated(1)),
),
if (min < 0 && max > 0)
const Align(
alignment: AlignmentDirectional.center,
child: Text("0"),
),
Align(
alignment: AlignmentDirectional.centerEnd,
child: Text(max.toStringAsFixedTruncated(1)),
),
],
),
),
StatefulSlider(
key: key,
initialValue: initialValue.toDouble(),
min: min.toDouble(),
max: max.toDouble(),
onChangeEnd: onChangeEnd,
),
],
),
),
);
}
Widget _buildBrightnessOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.brightness.name),
min: -100,
max: 100,
initialValue:
(_filters[ColorToolType.brightness] as _BrightnessArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.brightness] = _BrightnessArguments(value);
_notifyFiltersChanged();
},
);
Widget _buildContrastOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.contrast.name),
min: -100,
max: 100,
initialValue:
(_filters[ColorToolType.contrast] as _ContrastArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.contrast] = _ContrastArguments(value);
_notifyFiltersChanged();
},
);
Widget _buildWhitePointOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.whitePoint.name),
min: -100,
max: 100,
initialValue:
(_filters[ColorToolType.whitePoint] as _WhitePointArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.whitePoint] = _WhitePointArguments(value);
_notifyFiltersChanged();
},
);
Widget _buildBlackPointOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.blackPoint.name),
min: -100,
max: 100,
initialValue:
(_filters[ColorToolType.blackPoint] as _BlackPointArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.blackPoint] = _BlackPointArguments(value);
_notifyFiltersChanged();
},
);
Widget _buildSaturationOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.saturation.name),
min: -100,
max: 100,
initialValue:
(_filters[ColorToolType.saturation] as _SaturationArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.saturation] = _SaturationArguments(value);
_notifyFiltersChanged();
},
);
Widget _buildWarmthOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.warmth.name),
min: -100,
max: 100,
initialValue:
(_filters[ColorToolType.warmth] as _WarmthArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.warmth] = _WarmthArguments(value);
_notifyFiltersChanged();
},
);
Widget _buildTintOption(BuildContext context) => _buildSliderOption(
context,
key: Key(ColorToolType.tint.name),
min: -100,
max: 100,
initialValue: (_filters[ColorToolType.tint] as _TintArguments).value,
onChangeEnd: (value) {
_filters[ColorToolType.tint] = _TintArguments(value);
_notifyFiltersChanged();
},
);
void _onFilterPressed(ColorToolType type, ColorArguments defArgs) {
if (_selectedFilter == type) {
// deactivate filter
setState(() {
_selectedFilter = null;
_filters.remove(type);
});
} else {
setState(() {
_selectedFilter = type;
_filters[type] ??= defArgs;
});
}
_notifyFiltersChanged();
}
void _onBrightnessPressed() =>
_onFilterPressed(ColorToolType.brightness, const _BrightnessArguments(0));
void _onContrastPressed() =>
_onFilterPressed(ColorToolType.contrast, const _ContrastArguments(0));
void _onWhitePointPressed() =>
_onFilterPressed(ColorToolType.whitePoint, const _WhitePointArguments(0));
void _onBlackPointPressed() =>
_onFilterPressed(ColorToolType.blackPoint, const _BlackPointArguments(0));
void _onSaturationPressed() =>
_onFilterPressed(ColorToolType.saturation, const _SaturationArguments(0));
void _onWarmthPressed() =>
_onFilterPressed(ColorToolType.warmth, const _WarmthArguments(0));
void _onTintPressed() =>
_onFilterPressed(ColorToolType.tint, const _TintArguments(0));
void _notifyFiltersChanged() {
widget.onActiveFiltersChanged.call(_filters.values);
}
final _filters = <ColorToolType, ColorArguments>{};
ColorToolType? _selectedFilter;
}
class _BrightnessArguments implements ColorArguments {
const _BrightnessArguments(this.value);
@override
toImageFilter() => ColorBrightnessFilter(value / 100);
@override
_getToolType() => ColorToolType.brightness;
final double value;
}
class _ContrastArguments implements ColorArguments {
const _ContrastArguments(this.value);
@override
toImageFilter() => ColorContrastFilter(value / 100);
@override
_getToolType() => ColorToolType.contrast;
final double value;
}
class _WhitePointArguments implements ColorArguments {
const _WhitePointArguments(this.value);
@override
toImageFilter() => ColorWhitePointFilter(value / 100);
@override
_getToolType() => ColorToolType.whitePoint;
final double value;
}
class _BlackPointArguments implements ColorArguments {
const _BlackPointArguments(this.value);
@override
toImageFilter() => ColorBlackPointFilter(value / 100);
@override
_getToolType() => ColorToolType.blackPoint;
final double value;
}
class _SaturationArguments implements ColorArguments {
const _SaturationArguments(this.value);
@override
toImageFilter() => ColorSaturationFilter(value / 100);
@override
_getToolType() => ColorToolType.saturation;
final double value;
}
class _WarmthArguments implements ColorArguments {
const _WarmthArguments(this.value);
@override
toImageFilter() => ColorWarmthFilter(value / 100);
@override
_getToolType() => ColorToolType.warmth;
final double value;
}
class _TintArguments implements ColorArguments {
const _TintArguments(this.value);
@override
toImageFilter() => ColorTintFilter(value / 100);
@override
_getToolType() => ColorToolType.tint;
final double value;
}

View file

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/theme.dart';
class ToolbarButton extends StatelessWidget {
const ToolbarButton({
Key? key,
required this.icon,
required this.label,
required this.onPressed,
this.isSelected = false,
this.activationOrder = -1,
}) : super(key: key);
@override
build(BuildContext context) {
final color = !isSelected && isActivated
? AppTheme.primarySwatchDark[900]!.withOpacity(0.4)
: AppTheme.primarySwatchDark[500]!.withOpacity(0.7);
return InkWell(
onTap: onPressed,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 64,
height: 64,
child: Stack(
fit: StackFit.expand,
children: [
AnimatedOpacity(
opacity: isSelected || isActivated ? 1 : 0,
duration: k.animationDurationNormal,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: color,
),
),
),
Center(
child: Icon(
icon,
size: 32,
color: isSelected
? Colors.white
: AppTheme.unfocusedIconColorDark,
),
),
if (isActivated)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Align(
alignment: Alignment.topCenter,
child: Text(
(activationOrder + 1).toString(),
style: TextStyle(
fontSize: 12,
color: isSelected
? Colors.white
: AppTheme.unfocusedIconColorDark,
),
),
),
),
],
),
),
const SizedBox(height: 8),
Text(
label,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color:
isSelected ? Colors.white : AppTheme.unfocusedIconColorDark,
),
),
],
),
),
);
}
bool get isActivated => activationOrder >= 0;
final IconData icon;
final String label;
final VoidCallback? onPressed;
final bool isSelected;
final int activationOrder;
}