Skip to content

Commit 621baec

Browse files
committed
feat: add image background and overlay layers with editing options
1 parent f530dcd commit 621baec

9 files changed

Lines changed: 318 additions & 126 deletions

File tree

lib/image_builder/core/image_layer.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import 'package:flutter/widgets.dart';
22

33
abstract class ImageLayer extends StatelessWidget {
4-
const ImageLayer({
5-
super.key,
6-
required this.nextLayer,
7-
});
4+
const ImageLayer({super.key, required this.nextLayer});
85

96
final ImageLayer? nextLayer;
107

8+
@mustCallSuper
119
List<Widget> getEditingOptions(BuildContext context) {
1210
if (nextLayer == null) {
1311
return <Widget>[];
@@ -16,6 +14,7 @@ abstract class ImageLayer extends StatelessWidget {
1614
return nextLayer!.getEditingOptions(context);
1715
}
1816

17+
@mustCallSuper
1918
EdgeInsets getPadding() {
2019
if (nextLayer == null) {
2120
return EdgeInsets.zero;
@@ -24,6 +23,7 @@ abstract class ImageLayer extends StatelessWidget {
2423
return nextLayer!.getPadding();
2524
}
2625

26+
@mustCallSuper
2727
void dispose() {
2828
nextLayer?.dispose();
2929
}

lib/image_builder/layers/background.dart

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
import 'dart:io';
2+
13
import 'package:flutter/material.dart';
24
import 'package:heartry/image_builder/widgets/editing_option.dart';
5+
import 'package:image_picker/image_picker.dart';
6+
import 'package:material_symbols_icons/symbols.dart';
37

48
import '../../widgets/color_picker_dialog.dart';
59
import '../../widgets/gradient_palette_selector.dart';
610
import '../core/image_layer.dart';
711

812
class SolidBackgroundLayer extends ImageLayer {
9-
SolidBackgroundLayer({
10-
super.key,
11-
required super.nextLayer,
12-
});
13+
SolidBackgroundLayer({super.key, required super.nextLayer});
1314

1415
final color = ValueNotifier<Color?>(null);
1516

@@ -26,10 +27,7 @@ class SolidBackgroundLayer extends ImageLayer {
2627
return const SizedBox.shrink();
2728
}
2829

29-
return ColoredBox(
30-
color: colorValue,
31-
child: nextLayer!.build(context),
32-
);
30+
return ColoredBox(color: colorValue, child: nextLayer!.build(context));
3331
},
3432
);
3533
}
@@ -137,3 +135,63 @@ class GradientBackgroundLayer extends ImageLayer {
137135
super.dispose();
138136
}
139137
}
138+
139+
class ImageBackgroundLayer extends ImageLayer {
140+
ImageBackgroundLayer({super.key, super.nextLayer});
141+
142+
final filePath = ValueNotifier<XFile?>(null);
143+
144+
@override
145+
Widget build(BuildContext context) {
146+
return ValueListenableBuilder(
147+
valueListenable: filePath,
148+
builder: (context, value, child) {
149+
if (value == null) {
150+
return ColoredBox(
151+
color: Colors.white,
152+
child: nextLayer!.build(context),
153+
);
154+
}
155+
156+
return Container(
157+
decoration: BoxDecoration(
158+
image: DecorationImage(
159+
image: FileImage(File(filePath.value!.path)),
160+
fit: BoxFit.cover,
161+
),
162+
),
163+
child: nextLayer!.build(context),
164+
);
165+
},
166+
);
167+
}
168+
169+
@override
170+
List<Widget> getEditingOptions(BuildContext context) {
171+
return [
172+
EditingOption(
173+
option: "Background",
174+
icon: Icon(Symbols.image_search_rounded),
175+
tooltip: "Background Image",
176+
onPressed: () async {
177+
final picker = ImagePicker();
178+
final picked = await picker.pickImage(
179+
source: ImageSource.gallery,
180+
imageQuality: 90,
181+
);
182+
183+
if (picked != null) {
184+
filePath.value = picked;
185+
}
186+
},
187+
),
188+
...super.getEditingOptions(context),
189+
];
190+
}
191+
192+
@override
193+
void dispose() {
194+
filePath.dispose();
195+
super.dispose();
196+
}
197+
}

lib/image_builder/layers/overlay.dart

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,148 @@
11
import 'dart:io';
2+
import 'dart:ui';
23

34
import 'package:flutter/material.dart';
45
import 'package:flutter_riverpod/flutter_riverpod.dart';
6+
import 'package:material_symbols_icons/symbols.dart';
57

68
import '../../database/config.dart';
9+
import '../../widgets/color_picker_dialog.dart';
710
import '../core/image_layer.dart';
11+
import '../widgets/editing_option.dart';
12+
import '../widgets/slider_option.dart';
813

9-
class BubbleOverlayLayer extends ImageLayer {
10-
const BubbleOverlayLayer({super.key, super.nextLayer});
14+
class BlurOverlayLayer extends ImageLayer {
15+
BlurOverlayLayer({super.key, super.nextLayer});
16+
17+
final blur = ValueNotifier<double>(5.0);
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return ValueListenableBuilder(
22+
valueListenable: blur,
23+
builder: (context, value, child) {
24+
return BackdropFilter(
25+
filter: ImageFilter.blur(sigmaX: value, sigmaY: value),
26+
child: nextLayer!.build(context),
27+
);
28+
},
29+
);
30+
}
31+
32+
@override
33+
List<Widget> getEditingOptions(BuildContext context) {
34+
return [
35+
EditingOption(
36+
option: "Blur",
37+
icon: Icon(Symbols.blur_circular_rounded),
38+
tooltip: "Blur background",
39+
onPressed: () {
40+
showModalBottomSheet(
41+
context: context,
42+
builder: (context) => ValueListenableBuilder(
43+
valueListenable: blur,
44+
builder: (context, value, child) {
45+
return SliderOption(
46+
title: "Customize Blur",
47+
value: value,
48+
onChanged: (newValue) {
49+
blur.value = newValue;
50+
},
51+
min: 0,
52+
max: 20,
53+
divisions: 20,
54+
label: value.toStringAsFixed(0),
55+
);
56+
},
57+
),
58+
);
59+
},
60+
),
61+
...super.getEditingOptions(context),
62+
];
63+
}
64+
}
65+
66+
class TranlucentOverlayLayer extends ImageLayer {
67+
TranlucentOverlayLayer({super.key, required super.nextLayer});
68+
69+
final opacity = ValueNotifier<double>(0.2);
70+
final color = ValueNotifier<Color>(Colors.white);
1171

1272
@override
1373
Widget build(BuildContext context) {
14-
return _BubbleOverlayWidget(
74+
return ListenableBuilder(
75+
listenable: Listenable.merge([opacity, color]),
76+
builder: (context, child) => ColoredBox(
77+
color: color.value.withValues(alpha: opacity.value),
78+
child: child,
79+
),
1580
child: nextLayer!.build(context),
1681
);
1782
}
83+
84+
@override
85+
List<Widget> getEditingOptions(BuildContext context) {
86+
return [
87+
EditingOption(
88+
option: "Overlay Opacity",
89+
icon: Icon(Symbols.masked_transitions_rounded),
90+
tooltip: "Adjust Overlay Opacity",
91+
onPressed: () {
92+
showModalBottomSheet(
93+
context: context,
94+
builder: (context) => ValueListenableBuilder(
95+
valueListenable: opacity,
96+
builder: (context, value, child) {
97+
return SliderOption(
98+
title: "Customize Opacity",
99+
value: value * 100,
100+
label: "${(value * 100).toStringAsFixed(0)}%",
101+
onChanged: (newValue) {
102+
opacity.value = newValue / 100;
103+
},
104+
min: 0,
105+
max: 100,
106+
divisions: 10,
107+
);
108+
},
109+
),
110+
);
111+
},
112+
),
113+
EditingOption(
114+
option: "Overlay Color",
115+
icon: Icon(Icons.texture_rounded),
116+
tooltip: "Overlay Color",
117+
onPressed: () {
118+
showModalBottomSheet<void>(
119+
context: context,
120+
builder: (context) {
121+
return Padding(
122+
padding: const EdgeInsets.all(8.0),
123+
child: ColorPaletteSelector(
124+
currentColor: color.value,
125+
onColorSelected: (value) {
126+
color.value = value;
127+
},
128+
),
129+
);
130+
},
131+
);
132+
},
133+
),
134+
...super.getEditingOptions(context),
135+
];
136+
}
137+
}
138+
139+
class BubbleOverlayLayer extends ImageLayer {
140+
const BubbleOverlayLayer({super.key, super.nextLayer});
141+
142+
@override
143+
Widget build(BuildContext context) {
144+
return _BubbleOverlayWidget(child: nextLayer!.build(context));
145+
}
18146
}
19147

20148
class _BubbleOverlayWidget extends StatelessWidget {

lib/image_builder/layers/text.dart

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:heartry/image_builder/widgets/editing_option.dart';
3+
import 'package:heartry/image_builder/widgets/slider_option.dart';
34
import '../core/font_family.dart';
45
import 'package:material_symbols_icons/symbols.dart';
56

@@ -10,10 +11,7 @@ import '../core/image_controller.dart';
1011
import '../core/image_layer.dart';
1112

1213
class TextLayer extends ImageLayer {
13-
TextLayer({
14-
super.key,
15-
required this.controller,
16-
}) : super(nextLayer: null);
14+
TextLayer({super.key, required this.controller}) : super(nextLayer: null);
1715

1816
final _textScale = ValueNotifier<double>(1.0);
1917
final _textColor = ValueNotifier<Color?>(null);
@@ -80,7 +78,12 @@ class TextLayer extends ImageLayer {
8078
builder: (context) {
8179
return ValueListenableBuilder(
8280
valueListenable: _textScale,
83-
builder: (context, value, child) => _TextSizeHandler(
81+
builder: (context, value, child) => SliderOption(
82+
title: "Adjust Text Size",
83+
min: 0.8,
84+
max: 2,
85+
divisions: 12,
86+
label: "${value.toStringAsPrecision(2)}x",
8487
value: value,
8588
onChanged: (value) {
8689
controller.textSizeFactor = value;
@@ -116,7 +119,7 @@ class TextLayer extends ImageLayer {
116119
);
117120
},
118121
),
119-
...super.getEditingOptions(context)
122+
...super.getEditingOptions(context),
120123
];
121124
}
122125

@@ -129,48 +132,8 @@ class TextLayer extends ImageLayer {
129132
}
130133
}
131134

132-
class _TextSizeHandler extends StatelessWidget {
133-
const _TextSizeHandler({
134-
required this.value,
135-
required this.onChanged,
136-
});
137-
138-
final double value;
139-
final ValueChanged<double> onChanged;
140-
141-
@override
142-
Widget build(BuildContext context) {
143-
return SizedBox(
144-
height: 150,
145-
child: Padding(
146-
padding: const EdgeInsets.all(20.0),
147-
child: Column(
148-
mainAxisSize: MainAxisSize.max,
149-
children: [
150-
Text(
151-
"${value.toStringAsPrecision(2)}x",
152-
style: TextStyle(fontSize: 20),
153-
),
154-
Slider(
155-
value: value,
156-
min: 0.8,
157-
max: 2,
158-
divisions: 12,
159-
label: value.toStringAsPrecision(2),
160-
onChanged: onChanged,
161-
),
162-
],
163-
),
164-
),
165-
);
166-
}
167-
}
168-
169135
class _FontFamilyHandler extends StatelessWidget {
170-
const _FontFamilyHandler({
171-
required this.value,
172-
required this.onChanged,
173-
});
136+
const _FontFamilyHandler({required this.value, required this.onChanged});
174137

175138
final FontFamily value;
176139
final ValueChanged<FontFamily> onChanged;
@@ -194,10 +157,7 @@ class _FontFamilyHandler extends StatelessWidget {
194157
shape: RoundedRectangleBorder(
195158
borderRadius: BorderRadius.circular(12),
196159
side: isSelected
197-
? BorderSide(
198-
color: colorScheme.primary,
199-
width: 2,
200-
)
160+
? BorderSide(color: colorScheme.primary, width: 2)
201161
: BorderSide.none,
202162
),
203163
title: Text(

0 commit comments

Comments
 (0)