manu changes and added setting
BIN
android/app/src/main/res/drawable-hdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
android/app/src/main/res/drawable-mdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
android/app/src/main/res/drawable-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
android/app/src/main/res/drawable-night-hdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
android/app/src/main/res/drawable-night-mdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 674 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
BIN
android/app/src/main/res/drawable-v21/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -1,12 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
BIN
android/app/src/main/res/drawable-xhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
android/app/src/main/res/drawable-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 674 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 674 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
android/app/src/main/res/drawable/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -1,12 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
22
android/app/src/main/res/values-night-v31/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#121212</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -5,6 +5,10 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
22
android/app/src/main/res/values-v31/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#ffffff</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -5,6 +5,10 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
flutter_icons:
|
||||
android: "launcher_icon"
|
||||
ios: true
|
||||
image_path: "assets/launcher_icon.png"
|
||||
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 674 KiB |
@@ -38,7 +38,7 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="512" height="512"/>
|
||||
<image name="LaunchImage" width="480" height="480"/>
|
||||
<image name="LaunchBackground" width="1" height="1"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
21
lib/application/providers/brush_settings_provider.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:doddle/domain/models/effects/settings/brush_settings_state.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final brushSettingsProvider = StateNotifierProvider.family<BrushSettingsNotifier, BrushSettingsState, PenTool>((ref, penTool) {
|
||||
return BrushSettingsNotifier(penTool);
|
||||
});
|
||||
|
||||
class BrushSettingsNotifier extends StateNotifier<BrushSettingsState> {
|
||||
BrushSettingsNotifier(PenTool penTool) : super(BrushSettingsState(penTool: penTool));
|
||||
|
||||
void updateSetting(String key, dynamic value) {
|
||||
state = state.copyWith(
|
||||
values: {...state.values, key: value},
|
||||
);
|
||||
}
|
||||
|
||||
void resetToDefault() {
|
||||
state = BrushSettingsState(penTool: state.penTool);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,55 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:doddle/application/providers/brush_settings_provider.dart';
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:doddle/domain/models/effects/pen_effect.dart';
|
||||
import 'package:doddle/domain/models/effects/settings/brush_settings_state.dart';
|
||||
import 'package:doddle/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GlowEffect extends PenEffect {
|
||||
BrushSettingsState get settings => globalRef.read(brushSettingsProvider(PenTool.glowPen));
|
||||
double get intensity => settings.getValue('intensity') ?? 0.5;
|
||||
double get blur => settings.getValue('blur') ?? 3.0;
|
||||
double get outerGlow => settings.getValue('outerGlow') ?? 5.0;
|
||||
bool get innerGlow => settings.getValue('innerGlow') ?? true;
|
||||
bool get rainbow => settings.getValue('rainbow') ?? false;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Path path, Paint paint) {
|
||||
// Draw outer glow
|
||||
if (outerGlow > 0) {
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..maskFilter = MaskFilter.blur(BlurStyle.normal, blur)
|
||||
..color = rainbow ? _getRainbowColor() : drawController.currentColor.withOpacity(intensity)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = drawController.penSize! + outerGlow,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw inner glow if enabled
|
||||
if (innerGlow) {
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..maskFilter = MaskFilter.blur(BlurStyle.inner, blur * 0.5)
|
||||
..color = rainbow ? _getRainbowColor() : drawController.currentColor.withOpacity(intensity * 0.7)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = drawController.penSize!,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the main stroke
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0)
|
||||
..color = drawController.currentColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = drawController.penSize! + 5,
|
||||
paint
|
||||
..color = Colors.white
|
||||
..strokeWidth = drawController.penSize! * 0.8,
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawPath(path, paint..color = Colors.white);
|
||||
Color _getRainbowColor() {
|
||||
final hue = (DateTime.now().millisecondsSinceEpoch / 50) % 360;
|
||||
return HSVColor.fromAHSV(1.0, hue, 1.0, 1.0).toColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
import 'package:doddle/application/providers/brush_settings_provider.dart';
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:doddle/domain/models/effects/pen_effect.dart';
|
||||
import 'package:doddle/domain/models/effects/settings/brush_settings_state.dart';
|
||||
import 'package:doddle/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_drawing/path_drawing.dart';
|
||||
|
||||
class GlowWithDotsEffect extends PenEffect {
|
||||
BrushSettingsState get settings =>
|
||||
globalRef.read(brushSettingsProvider(PenTool.glowWithDotsPen));
|
||||
double get dashLength => settings.getValue('dashLength') ?? 5.0;
|
||||
double get gapLength => settings.getValue('gapLength') ?? 10.0;
|
||||
double get glowRadius => settings.getValue('glowRadius') ?? 5.0;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Path path, Paint paint) {
|
||||
|
||||
final pathWithDots = dashPath(
|
||||
path,
|
||||
dashArray: CircularIntervalList<double>(<double>[5
|
||||
, 10]), // Changed to create 5px dashes with 10px gaps
|
||||
);
|
||||
final pathWithDots = dashPath(
|
||||
path,
|
||||
dashArray: CircularIntervalList<double>(<double>[dashLength, gapLength]),
|
||||
);
|
||||
|
||||
canvas.drawPath(
|
||||
pathWithDots,
|
||||
Paint()
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5.0)
|
||||
..maskFilter = MaskFilter.blur(BlurStyle.normal, glowRadius)
|
||||
..color = drawController.currentColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 5.0);
|
||||
..strokeWidth = drawController.penSize!);
|
||||
|
||||
canvas.drawPath(
|
||||
pathWithDots,
|
||||
paint..strokeWidth = drawController.penSize!);
|
||||
paint
|
||||
..strokeWidth = drawController.penSize!
|
||||
..color = Colors.white);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,8 @@ import 'package:doddle/application/providers/canvas/canvas_provider.dart';
|
||||
|
||||
abstract class PenEffect {
|
||||
DrawController get drawController => globalRef.read(canvasNotifierProvider);
|
||||
|
||||
|
||||
// void initialize(Ref ref) {
|
||||
// drawController = ref.read(canvasNotifierProvider);
|
||||
// }
|
||||
|
||||
|
||||
void paint(Canvas canvas, Path path, Paint paint);
|
||||
|
||||
// New method to handle point additions
|
||||
|
||||
157
lib/domain/models/effects/settings/brush_settings_config.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum SettingType {
|
||||
slider,
|
||||
toggle,
|
||||
color,
|
||||
// Add more types as needed
|
||||
}
|
||||
|
||||
class BrushSettingConfig {
|
||||
final String label;
|
||||
final SettingType type;
|
||||
final dynamic defaultValue;
|
||||
final dynamic minValue;
|
||||
final dynamic maxValue;
|
||||
final int? divisions;
|
||||
final IconData? icon;
|
||||
|
||||
const BrushSettingConfig({
|
||||
required this.label,
|
||||
required this.type,
|
||||
required this.defaultValue,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
this.divisions,
|
||||
this.icon,
|
||||
});
|
||||
}
|
||||
|
||||
class BrushConfig {
|
||||
final Map<String, BrushSettingConfig> settings;
|
||||
final String name;
|
||||
final String description;
|
||||
|
||||
const BrushConfig({
|
||||
required this.name,
|
||||
required this.settings,
|
||||
this.description = '',
|
||||
});
|
||||
}
|
||||
|
||||
// Define configurations for each brush type
|
||||
class BrushConfigs {
|
||||
static final Map<PenTool, BrushConfig> configs = {
|
||||
PenTool.normalPen: BrushConfig(
|
||||
name: 'Normal Brush',
|
||||
settings: {}, // Normal pen has no special settings, just uses the base color and size
|
||||
),
|
||||
PenTool.glowWithDotsPen: BrushConfig(
|
||||
name: 'Glow with Dots Brush',
|
||||
settings: {
|
||||
'dashLength': BrushSettingConfig(
|
||||
label: 'Dash Length',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 5.0,
|
||||
minValue: 1.0,
|
||||
maxValue: 20.0,
|
||||
icon: Icons.horizontal_rule,
|
||||
),
|
||||
'gapLength': BrushSettingConfig(
|
||||
label: 'Gap Length',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 10.0,
|
||||
minValue: 1.0,
|
||||
maxValue: 30.0,
|
||||
icon: Icons.space_bar,
|
||||
),
|
||||
'glowRadius': BrushSettingConfig(
|
||||
label: 'Glow Radius',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 5.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 20.0,
|
||||
icon: Icons.blur_on,
|
||||
),
|
||||
},
|
||||
),
|
||||
PenTool.sprayPen: BrushConfig(
|
||||
name: 'Spray Brush',
|
||||
settings: {
|
||||
'density': BrushSettingConfig(
|
||||
label: 'Density',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 10.0,
|
||||
minValue: 1.0,
|
||||
maxValue: 30.0,
|
||||
icon: Icons.grain,
|
||||
),
|
||||
'spread': BrushSettingConfig(
|
||||
label: 'Spread',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 10.0,
|
||||
minValue: 1.0,
|
||||
maxValue: 50.0,
|
||||
icon: Icons.radio_button_unchecked,
|
||||
),
|
||||
'opacity': BrushSettingConfig(
|
||||
label: 'Opacity',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 0.3,
|
||||
minValue: 0.1,
|
||||
maxValue: 1.0,
|
||||
icon: Icons.opacity,
|
||||
),
|
||||
'randomizeEachDot': BrushSettingConfig(
|
||||
label: 'Randomize Each Dot',
|
||||
type: SettingType.toggle,
|
||||
defaultValue: false,
|
||||
icon: Icons.color_lens,
|
||||
),
|
||||
},
|
||||
),
|
||||
PenTool.glowPen: BrushConfig(
|
||||
name: 'Glow Brush',
|
||||
settings: {
|
||||
'intensity': BrushSettingConfig(
|
||||
label: 'Glow Intensity',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 0.5,
|
||||
minValue: 0.1,
|
||||
maxValue: 1.0,
|
||||
icon: Icons.brightness_medium,
|
||||
),
|
||||
'blur': BrushSettingConfig(
|
||||
label: 'Blur Amount',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 3.0,
|
||||
minValue: 1.0,
|
||||
maxValue: 10.0,
|
||||
icon: Icons.blur_on,
|
||||
),
|
||||
'outerGlow': BrushSettingConfig(
|
||||
label: 'Outer Glow Size',
|
||||
type: SettingType.slider,
|
||||
defaultValue: 5.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 20.0,
|
||||
icon: Icons.radio_button_unchecked,
|
||||
),
|
||||
'innerGlow': BrushSettingConfig(
|
||||
label: 'Inner Glow',
|
||||
type: SettingType.toggle,
|
||||
defaultValue: true,
|
||||
icon: Icons.circle,
|
||||
),
|
||||
'rainbow': BrushSettingConfig(
|
||||
label: 'Rainbow Mode',
|
||||
type: SettingType.toggle,
|
||||
defaultValue: false,
|
||||
icon: Icons.color_lens,
|
||||
),
|
||||
},
|
||||
),
|
||||
// Add more brush configurations...
|
||||
};
|
||||
}
|
||||
34
lib/domain/models/effects/settings/brush_settings_state.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:doddle/domain/models/effects/settings/brush_settings_config.dart';
|
||||
|
||||
class BrushSettingsState {
|
||||
final Map<String, dynamic> values;
|
||||
final PenTool penTool;
|
||||
|
||||
BrushSettingsState({
|
||||
required this.penTool,
|
||||
Map<String, dynamic>? values,
|
||||
}) : values = values ?? _getDefaultValues(penTool);
|
||||
|
||||
static Map<String, dynamic> _getDefaultValues(PenTool penTool) {
|
||||
final config = BrushConfigs.configs[penTool];
|
||||
if (config == null) return {};
|
||||
|
||||
return Map.fromEntries(
|
||||
config.settings.entries.map(
|
||||
(e) => MapEntry(e.key, e.value.defaultValue),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BrushSettingsState copyWith({
|
||||
Map<String, dynamic>? values,
|
||||
}) {
|
||||
return BrushSettingsState(
|
||||
penTool: penTool,
|
||||
values: values ?? this.values,
|
||||
);
|
||||
}
|
||||
|
||||
dynamic getValue(String key) => values[key];
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'dart:ui';
|
||||
import 'package:doddle/application/providers/brush_settings_provider.dart';
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:doddle/domain/models/effects/settings/brush_settings_state.dart';
|
||||
import 'package:doddle/domain/models/point.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:doddle/main.dart';
|
||||
import 'package:doddle/domain/models/effects/pen_effect.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
@@ -21,21 +24,25 @@ class SprayDot {
|
||||
class SprayEffect extends PenEffect {
|
||||
final random = math.Random();
|
||||
List<SprayDot> sprayDots = [];
|
||||
|
||||
BrushSettingsState get settings => globalRef.read(brushSettingsProvider(PenTool.sprayPen));
|
||||
|
||||
double get density => settings.getValue('density') ?? 10;
|
||||
double get spread => settings.getValue('spread') ?? 10;
|
||||
double get opacity => settings.getValue('opacity') ?? 0.3;
|
||||
bool get randomizeEachDot => settings.getValue('randomizeEachDot') ?? false;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Path path, Paint paint) {
|
||||
print('density: $density, spread: $spread, opacity: $opacity');
|
||||
|
||||
for (var dot in sprayDots) {
|
||||
// Get all symmetrical positions for this dot
|
||||
final symmetricalDots = getSymmetricalPositions(dot.position);
|
||||
|
||||
// Draw a dot at each symmetrical position
|
||||
|
||||
for (var position in symmetricalDots) {
|
||||
canvas.drawCircle(
|
||||
position,
|
||||
dot.size * (drawController.penSize ?? 2.0),
|
||||
paint
|
||||
..color = dot.color.withOpacity(dot.opacity)
|
||||
);
|
||||
canvas.drawCircle(position, dot.size * (drawController.penSize ?? 2.0),
|
||||
paint..color = dot.color.withOpacity(dot.opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,22 +50,24 @@ class SprayEffect extends PenEffect {
|
||||
@override
|
||||
void onPointAdd(Point point) {
|
||||
if (point.offset == null) return;
|
||||
|
||||
// Create multiple spray dots around the point
|
||||
final numDots = 10; // Adjust this for more/less density
|
||||
|
||||
final numDots = density.toInt();
|
||||
for (int i = 0; i < numDots; i++) {
|
||||
final spread = 10.0; // Adjust this for wider/narrower spray
|
||||
final randomOffset = Offset(
|
||||
random.nextDouble() * spread - spread/2,
|
||||
random.nextDouble() * spread - spread/2
|
||||
);
|
||||
|
||||
final randomOffset = Offset(random.nextDouble() * spread - spread / 2,
|
||||
random.nextDouble() * spread - spread / 2);
|
||||
|
||||
final color = randomizeEachDot
|
||||
? getRandomColor()
|
||||
: (drawController.isRandomColor
|
||||
? getRandomColor()
|
||||
: drawController.currentColor);
|
||||
|
||||
addSprayDots([
|
||||
SprayDot(
|
||||
position: point.offset! + randomOffset,
|
||||
opacity: random.nextDouble() * 0.3 + 0.1, // Random opacity between 0.1 and 0.4
|
||||
size: random.nextDouble() * 0.5 + 0.5, // Random size between 0.5 and 1.0
|
||||
color: drawController.isRandomColor ? getRandomColor() : drawController.currentColor,
|
||||
opacity: random.nextDouble() * opacity,
|
||||
size: random.nextDouble() * 0.5 + 0.5,
|
||||
color: color,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import 'package:doddle/application/providers/canvas/canvas_provider.dart';
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:doddle/domain/models/effects/settings/brush_settings_config.dart';
|
||||
import 'package:doddle/application/providers/brush_settings_provider.dart';
|
||||
import 'package:doddle/presentation/common/widgets/brush_settings/brush_viewer.dart';
|
||||
|
||||
class BrushSettingsPanel extends ConsumerWidget {
|
||||
const BrushSettingsPanel({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currentPenTool = ref.watch(canvasNotifierProvider).penTool;
|
||||
if (currentPenTool == null) return const SizedBox.shrink();
|
||||
|
||||
final brushConfig = BrushConfigs.configs[currentPenTool];
|
||||
if (brushConfig == null) return const SizedBox.shrink();
|
||||
|
||||
final settings = ref.watch(brushSettingsProvider(currentPenTool));
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
brushConfig.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
if (brushConfig.description.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
brushConfig.description,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
const BrushPreview(),
|
||||
const SizedBox(height: 16),
|
||||
...brushConfig.settings.entries.map((entry) {
|
||||
return _buildSettingControl(
|
||||
context,
|
||||
ref,
|
||||
currentPenTool,
|
||||
entry.key,
|
||||
entry.value,
|
||||
settings.getValue(entry.key),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
_buildResetButton(context, ref, currentPenTool),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingControl(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
PenTool penTool,
|
||||
String key,
|
||||
BrushSettingConfig config,
|
||||
dynamic currentValue,
|
||||
) {
|
||||
switch (config.type) {
|
||||
case SettingType.slider:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (config.icon != null) ...[
|
||||
Icon(config.icon, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(config.label),
|
||||
const Spacer(),
|
||||
Text(currentValue.toStringAsFixed(1)),
|
||||
],
|
||||
),
|
||||
Slider(
|
||||
value: currentValue,
|
||||
min: config.minValue,
|
||||
max: config.maxValue,
|
||||
divisions: config.divisions,
|
||||
onChanged: (value) {
|
||||
ref.read(brushSettingsProvider(penTool).notifier)
|
||||
.updateSetting(key, value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case SettingType.toggle:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
if (config.icon != null) ...[
|
||||
Icon(config.icon, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(config.label),
|
||||
const Spacer(),
|
||||
Switch.adaptive(
|
||||
value: currentValue ?? false,
|
||||
onChanged: (value) {
|
||||
ref.read(brushSettingsProvider(penTool).notifier)
|
||||
.updateSetting(key, value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildResetButton(BuildContext context, WidgetRef ref, PenTool penTool) {
|
||||
return Center(
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.restart_alt),
|
||||
label: const Text('Reset to Default'),
|
||||
onPressed: () {
|
||||
ref.read(brushSettingsProvider(penTool).notifier).resetToDefault();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:doddle/presentation/painting/brush_preview_painter.dart';
|
||||
import 'package:sizer/sizer.dart';
|
||||
|
||||
class BrushPreview extends ConsumerWidget {
|
||||
const BrushPreview({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Container(
|
||||
height: 150.h,
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: BrushPreviewPainter(ref),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class BrushToolGrid extends ConsumerWidget {
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
ref.read(canvasNotifierProvider.notifier).changePenTool(brush.penTool);
|
||||
Navigator.of(context).pop();
|
||||
// Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'brush_tool_grid.dart';
|
||||
import 'color_tool_grid.dart';
|
||||
import 'symmetry_tool_grid.dart';
|
||||
import 'canvas_settings_tool_grid.dart';
|
||||
import 'package:doddle/presentation/common/widgets/brush_settings/brush_settings_panel.dart';
|
||||
|
||||
class ToolsWidget extends ConsumerWidget {
|
||||
const ToolsWidget({Key? key}) : super(key: key);
|
||||
@@ -84,7 +85,13 @@ class ToolsWidget extends ConsumerWidget {
|
||||
required ToolType toolType,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: () => _showToolSettings(context, toolType),
|
||||
onTap: () {
|
||||
if (toolType == ToolType.brushs) {
|
||||
_showBrushSettings(context);
|
||||
} else {
|
||||
_showToolSettings(context, toolType);
|
||||
}
|
||||
},
|
||||
child: icon,
|
||||
);
|
||||
}
|
||||
@@ -110,6 +117,24 @@ class ToolsWidget extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showBrushSettings(BuildContext context) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BrushToolGrid(),
|
||||
Divider(),
|
||||
BrushSettingsPanel(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildToolContent(ToolType toolType) {
|
||||
switch (toolType) {
|
||||
case ToolType.brushs:
|
||||
|
||||
75
lib/presentation/painting/brush_preview_painter.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:doddle/application/providers/canvas/canvas_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:doddle/domain/models/draw_controller.dart';
|
||||
import 'package:doddle/domain/models/point.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class BrushPreviewPainter extends CustomPainter {
|
||||
final WidgetRef ref;
|
||||
|
||||
BrushPreviewPainter(this.ref);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
canvas.save();
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
canvas.translate(center.dx, center.dy);
|
||||
|
||||
// Create a wavy line for preview
|
||||
final points = _generatePreviewPoints(size);
|
||||
final controller = DrawController(
|
||||
points: points,
|
||||
penTool: ref.read(canvasNotifierProvider).penTool,
|
||||
penSize: ref.read(canvasNotifierProvider).penSize,
|
||||
currentColor: ref.read(canvasNotifierProvider).currentColor,
|
||||
effects: ref.read(canvasNotifierProvider).effects,
|
||||
);
|
||||
|
||||
final effect = controller.effects[controller.penTool];
|
||||
|
||||
if (effect != null) {
|
||||
Path path = Path();
|
||||
Paint paint = Paint()
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0;
|
||||
|
||||
_drawPreviewPoints(canvas, path, paint, effect, points);
|
||||
effect.paint(canvas, path, paint);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
List<Point?> _generatePreviewPoints(Size size) {
|
||||
final points = <Point?>[];
|
||||
final width = size.width * 0.4;
|
||||
|
||||
for (double x = -width/2; x <= width/2; x += 60) {
|
||||
final y = math.sin(x * 0.1) * 20;
|
||||
points.add(Point(offset: Offset(x, y)));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
void _drawPreviewPoints(Canvas canvas, Path path, Paint paint, dynamic effect, List<Point?> points) {
|
||||
for (var j = 0; j < points.length - 1; j++) {
|
||||
final currentPoint = points[j]?.offset;
|
||||
final nextPoint = points[j + 1]?.offset;
|
||||
|
||||
if (currentPoint != null && nextPoint != null) {
|
||||
final currentSymPoints = effect.getSymmetricalPositions(currentPoint);
|
||||
final nextSymPoints = effect.getSymmetricalPositions(nextPoint);
|
||||
|
||||
for (var i = 0; i < currentSymPoints.length; i++) {
|
||||
path.moveTo(currentSymPoints[i].dx, currentSymPoints[i].dy);
|
||||
path.lineTo(nextSymPoints[i].dx, nextSymPoints[i].dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
||||
@@ -10,7 +10,8 @@ class LastImageAsBackground extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (image != null) {
|
||||
canvas.drawImage(image!, Offset.zero, ui.Paint()
|
||||
// ..filterQuality = ui.FilterQuality.high
|
||||
..blendMode = BlendMode.srcIn
|
||||
..filterQuality = ui.FilterQuality.high
|
||||
// ..isAntiAlias = true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -487,10 +487,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
|
||||
sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.1"
|
||||
version: "0.14.2"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
||||
14
pubspec.yaml
@@ -54,7 +54,7 @@ dependencies:
|
||||
firebase_database: ^11.1.6
|
||||
# google_mobile_ads: ^5.2.0
|
||||
screen_recorder: ^0.3.0
|
||||
flutter_native_splash: ^2.2.6
|
||||
flutter_native_splash: ^2.4.3
|
||||
sizer: ^3.0.5
|
||||
shared_preferences: ^2.3.3
|
||||
go_router: ^14.6.1
|
||||
@@ -64,6 +64,16 @@ dependencies:
|
||||
flutter_native_splash:
|
||||
color: "#000000"
|
||||
image: assets/animation_logo.gif
|
||||
android_12:
|
||||
image: assets/animation_logo.gif
|
||||
icon_background_color: "#ffffff"
|
||||
image_dark: assets/animation_logo.gif
|
||||
icon_background_color_dark: "#121212"
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: "launcher_icon"
|
||||
ios: true
|
||||
image_path: "assets/launcher_icon.png"
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -76,7 +86,7 @@ dev_dependencies:
|
||||
# rules and activating additional ones.
|
||||
build_runner: null
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_launcher_icons: ^0.14.1
|
||||
flutter_launcher_icons: ^0.14.2
|
||||
flutter_gen_runner: ^5.8.0
|
||||
riverpod_generator: ^2.6.1
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!DOCTYPE html><html><head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
@@ -27,20 +25,89 @@
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
|
||||
<title>doddle</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<script src="splash/splash.js"></script>
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
|
||||
<link rel="stylesheet" type="text/css" href="splash/style.css">
|
||||
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||
|
||||
|
||||
|
||||
<style id="splash-screen-style">
|
||||
html {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
background-color: #000000;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.contain {
|
||||
display:block;
|
||||
width:100%; height:100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.stretch {
|
||||
display:block;
|
||||
width:100%; height:100%;
|
||||
}
|
||||
|
||||
.cover {
|
||||
display:block;
|
||||
width:100%; height:100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
-ms-transform: translate(-50%, 0);
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.bottomLeft {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bottomRight {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
<script id="splash-screen-script">
|
||||
function removeSplashFromWeb() {
|
||||
document.getElementById("splash")?.remove();
|
||||
document.getElementById("splash-branding")?.remove();
|
||||
document.body.style.background = "transparent";
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<picture id="splash">
|
||||
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)">
|
||||
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
|
||||
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""/>
|
||||
<source srcset="splash/img/light-1x.gif 1x, splash/img/light-2x.gif 2x, splash/img/light-3x.gif 3x, splash/img/light-4x.gif 4x" media="(prefers-color-scheme: light)">
|
||||
<source srcset="splash/img/dark-1x.gif 1x, splash/img/dark-2x.gif 2x, splash/img/dark-3x.gif 3x, splash/img/dark-4x.gif 4x" media="(prefers-color-scheme: dark)">
|
||||
<img class="center" aria-hidden="true" src="splash/img/light-1x.gif" alt="">
|
||||
</picture>
|
||||
|
||||
|
||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||
application. For more information, see:
|
||||
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||
@@ -108,5 +175,6 @@
|
||||
loadMainDartJs();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
</body></html>
|
||||
BIN
web/splash/img/dark-1x.gif
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
web/splash/img/dark-2x.gif
Normal file
|
After Width: | Height: | Size: 436 KiB |
BIN
web/splash/img/dark-3x.gif
Normal file
|
After Width: | Height: | Size: 880 KiB |
BIN
web/splash/img/dark-4x.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
web/splash/img/light-1x.gif
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
web/splash/img/light-2x.gif
Normal file
|
After Width: | Height: | Size: 436 KiB |
BIN
web/splash/img/light-3x.gif
Normal file
|
After Width: | Height: | Size: 880 KiB |
BIN
web/splash/img/light-4x.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |