Skip to content

Commit dc4105d

Browse files
authored
Merge pull request #2 from PatrykIti/fix/picker_color_closes
Refactor: Use EventChannel for real-time color updates and fix picker closing issue
2 parents 674e2a4 + 1baea06 commit dc4105d

File tree

9 files changed

+179
-132
lines changed

9 files changed

+179
-132
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 0.0.2
2+
3+
* **BREAKING CHANGE:** Refactored the plugin to use an `EventChannel` for color updates.
4+
* `NativeIosColorPicker.showColorPicker()` is now `Future<void>` and only shows the picker.
5+
* Added `NativeIosColorPicker.onColorChanged` stream (`Stream<Map<dynamic, dynamic>>`) which emits color updates (RGBA map) from the native picker in real-time.
6+
* Fixed an issue where the picker might disappear immediately after selection by adopting a stream-based approach for color updates.
7+
* Updated `README.md` with new usage instructions and API details reflecting the stream-based approach.
8+
* Improved `ColorModel.fromMap` to handle dynamic map types safely from the stream.
9+
10+
111
## 0.0.1
212

313
* TODO: Describe initial release.

README.md

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ A Flutter plugin that provides a native iOS color picker interface using the UIC
44

55
## Features
66

7-
- Native iOS color picker UI
8-
- Returns RGBA color values
9-
- Supports iOS 14+ devices
10-
- Simple and easy-to-use API
7+
- Native iOS/macOS color picker UI (macOS implementation might need verification based on provided code)
8+
- Streams RGBA color values in real-time from the picker
9+
- Supports iOS 14+ and potentially macOS 10.15+ (requires native implementation verification)
10+
- Simple API using MethodChannel for showing the picker and EventChannel for color updates
1111

1212
## Requirements
1313

@@ -20,7 +20,7 @@ Add this to your package's `pubspec.yaml` file:
2020

2121
```yaml
2222
dependencies:
23-
native_ios_color_picker: ^0.0.1
23+
native_ios_color_picker: ^0.0.2
2424
```
2525
2626
Then run:
@@ -34,69 +34,116 @@ $ flutter pub get
3434
First, import the package:
3535

3636
```dart
37+
import 'dart:async'; // Required for StreamSubscription
38+
import 'package:flutter/material.dart'; // Required for Color
3739
import 'package:native_ios_color_picker/native_ios_color_picker.dart';
3840
```
3941

40-
Then, you can show the color picker using the static method `showColorPicker()`:
42+
The color picker is shown using `NativeIosColorPicker.showColorPicker()`. Color changes are received via the `NativeIosColorPicker.onColorChanged` stream. You need to subscribe to this stream to get color updates.
4143

4244
```dart
43-
try {
44-
final colorValues = await NativeIosColorPicker.showColorPicker();
45-
print('Selected color: $colorValues');
46-
// colorValues contains:
47-
// {
48-
// 'red': 0.5, // value between 0.0 and 1.0
49-
// 'green': 0.3, // value between 0.0 and 1.0
50-
// 'blue': 0.7, // value between 0.0 and 1.0
51-
// 'alpha': 1.0 // value between 0.0 and 1.0
52-
// }
53-
} catch (e) {
54-
print('Error showing color picker: $e');
45+
StreamSubscription? _colorSubscription;
46+
Color _selectedColor = Colors.blue; // Initial color
47+
48+
@override
49+
void initState() {
50+
super.initState();
51+
// Subscribe to the color changes stream
52+
_colorSubscription = NativeIosColorPicker.onColorChanged.listen((colorMap) {
53+
// The stream emits a Map<dynamic, dynamic>, convert it safely
54+
// You can use the ColorModel for conversion (see below)
55+
final newColor = Color.fromRGBO(
56+
((colorMap['red'] ?? 0.0) * 255).toInt(),
57+
((colorMap['green'] ?? 0.0) * 255).toInt(),
58+
((colorMap['blue'] ?? 0.0) * 255).toInt(),
59+
(colorMap['alpha'] ?? 1.0),
60+
);
61+
setState(() {
62+
_selectedColor = newColor;
63+
});
64+
print('Color updated: $_selectedColor');
65+
}, onError: (error) {
66+
print('Error receiving color: $error');
67+
});
5568
}
56-
```
5769
58-
### Screenshots
70+
@override
71+
void dispose() {
72+
// Cancel the subscription when the widget is disposed
73+
_colorSubscription?.cancel();
74+
super.dispose();
75+
}
5976
60-
<img src="https://github.com/squirelboy360/flutter-native-color-picker-plugin/blob/main/assets/iphone.png" width="300" alt="iOS Color Picker"/>
77+
// Somewhere in your widget build method or an event handler:
78+
void _openColorPicker() async {
79+
try {
80+
// Show the picker. It doesn't return the color directly anymore.
81+
await NativeIosColorPicker.showColorPicker();
82+
} catch (e) {
83+
print('Error showing color picker: $e');
84+
}
85+
}
6186
62-
<img src="https://github.com/squirelboy360/flutter-native-color-picker-plugin/blob/main/assets/macos.png" width="300" alt="macOS Color Picker"/>
87+
// Example button to open the picker
88+
ElevatedButton(
89+
onPressed: _openColorPicker,
90+
child: Text('Show Color Picker'),
91+
style: ElevatedButton.styleFrom(backgroundColor: _selectedColor),
92+
)
93+
94+
```
6395

6496
### Using with ColorModel
6597

66-
The package also provides a `ColorModel` class for easier color handling:
98+
The `ColorModel` class can simplify handling the map received from the stream:
6799

68100
```dart
69-
// Create from color picker result
70-
final colorValues = await NativeIosColorPicker.showColorPicker();
71-
final colorModel = ColorModel.fromMap(colorValues);
72-
73-
// Convert to Flutter Color
74-
final flutterColor = colorModel.toColor();
75-
76-
// Access individual components
77-
print('Red: ${colorModel.red}');
78-
print('Green: ${colorModel.green}');
79-
print('Blue: ${colorModel.blue}');
80-
print('Alpha: ${colorModel.alpha}');
101+
// Inside the stream listener:
102+
_colorSubscription = NativeIosColorPicker.onColorChanged.listen((colorMap) {
103+
// Ensure the map has the correct types before passing to fromMap
104+
final safeMap = Map<String, dynamic>.from(colorMap);
105+
final colorModel = ColorModel.fromMap(safeMap);
106+
final newColor = colorModel.toColor();
107+
108+
setState(() {
109+
_selectedColor = newColor;
110+
});
111+
112+
// Access individual components
113+
print('Red: ${colorModel.red}');
114+
print('Green: ${colorModel.green}');
115+
// ... etc.
116+
}, onError: // ...
117+
);
81118
```
82119

120+
### Screenshots
121+
122+
<img src="https://github.com/squirelboy360/flutter-native-color-picker-plugin/blob/main/assets/iphone.png" width="300" alt="iOS Color Picker"/>
123+
124+
<img src="https://github.com/squirelboy360/flutter-native-color-picker-plugin/blob/main/assets/macos.png" width="300" alt="macOS Color Picker"/>
125+
83126
## API Reference
84127

85128
### NativeIosColorPicker
86129

87-
#### `static Future<Map<String, double>> showColorPicker()`
130+
#### `static Future<void> showColorPicker()`
131+
132+
Shows the native iOS/macOS color picker window. This method does not return the selected color directly. Color updates are sent via the `onColorChanged` stream.
133+
134+
#### `static Stream<Map<dynamic, dynamic>> get onColorChanged`
88135

89-
Shows the native iOS color picker and returns the selected color values as a map containing RGBA components (values between 0.0 and 1.0).
136+
A broadcast stream that emits updates when the color is changed in the native picker. Each event is a `Map` containing RGBA components (keys: 'red', 'green', 'blue', 'alpha') with values between 0.0 and 1.0. You should subscribe to this stream to receive color updates.
90137

91138
### ColorModel
92139

93140
#### `ColorModel({required double red, required double green, required double blue, required double alpha})`
94141

95142
Creates a new ColorModel instance with the specified RGBA values (between 0.0 and 1.0).
96143

97-
#### `ColorModel.fromMap(Map<String, double> map)`
144+
#### `ColorModel.fromMap(Map<String, dynamic> map)`
98145

99-
Creates a ColorModel instance from a map containing RGBA values.
146+
Creates a ColorModel instance from a map containing RGBA values (typically received from the `onColorChanged` stream). Handles potential null values and converts numeric types safely.
100147

101148
#### `Color toColor()`
102149

ios/Classes/SwiftNativeIosColorPickerPlugin.swift

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,72 @@ import UIKit
33

44
@available(iOS 14.0, *)
55
public class SwiftNativeIosColorPickerPlugin: NSObject, FlutterPlugin {
6+
private var eventSink: FlutterEventSink?
7+
private var flutterResult: FlutterResult?
8+
69
public static func register(with registrar: FlutterPluginRegistrar) {
7-
let channel = FlutterMethodChannel(name: "native_ios_color_picker", binaryMessenger: registrar.messenger())
10+
let methodChannel = FlutterMethodChannel(name: "native_ios_color_picker", binaryMessenger: registrar.messenger())
811
let instance = SwiftNativeIosColorPickerPlugin()
9-
registrar.addMethodCallDelegate(instance, channel: channel)
12+
registrar.addMethodCallDelegate(instance, channel: methodChannel)
13+
14+
let eventChannel = FlutterEventChannel(name: "native_ios_color_picker/events", binaryMessenger: registrar.messenger())
15+
eventChannel.setStreamHandler(instance)
1016
}
11-
17+
1218
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
1319
switch call.method {
1420
case "showColorPicker":
15-
showColorPicker(result: result)
21+
showColorPicker()
22+
result(nil) // Picker is being shown, no immediate result
1623
default:
1724
result(FlutterMethodNotImplemented)
1825
}
1926
}
20-
21-
private func showColorPicker(result: @escaping FlutterResult) {
27+
28+
private func showColorPicker() {
2229
DispatchQueue.main.async {
2330
guard let viewController = UIApplication.shared.windows.first?.rootViewController else {
24-
result(FlutterError(code: "NO_VIEWCONTROLLER",
25-
message: "Could not get root view controller",
26-
details: nil))
2731
return
2832
}
29-
33+
3034
let colorPicker = UIColorPickerViewController()
3135
colorPicker.delegate = self
32-
self.flutterResult = result
3336
viewController.present(colorPicker, animated: true)
3437
}
3538
}
36-
37-
private var flutterResult: FlutterResult?
39+
}
40+
41+
@available(iOS 14.0, *)
42+
extension SwiftNativeIosColorPickerPlugin: FlutterStreamHandler {
43+
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
44+
self.eventSink = events
45+
return nil
46+
}
47+
48+
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
49+
self.eventSink = nil
50+
return nil
51+
}
3852
}
3953

4054
@available(iOS 14.0, *)
4155
extension SwiftNativeIosColorPickerPlugin: UIColorPickerViewControllerDelegate {
4256
public func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
57+
// Nothing to do – picker is dismissed, but live updates already sent via eventSink
58+
}
59+
60+
public func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
61+
guard let sink = eventSink else { return }
62+
4363
let color = viewController.selectedColor
4464
let colorDict: [String: Any] = [
4565
"red": Double(color.components.red),
4666
"green": Double(color.components.green),
4767
"blue": Double(color.components.blue),
4868
"alpha": Double(color.components.alpha)
4969
]
50-
flutterResult?(colorDict)
51-
flutterResult = nil
52-
}
53-
54-
public func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
55-
// Optional: Handle color selection changes in real-time
70+
71+
sink(colorDict) // live send to Flutter
5672
}
5773
}
5874

@@ -65,4 +81,4 @@ extension UIColor {
6581
getRed(&r, green: &g, blue: &b, alpha: &a)
6682
return (r, g, b, a)
6783
}
68-
}
84+
}

ios/native_ios_color_picker.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'native_ios_color_picker'
3-
s.version = '0.0.1'
3+
s.version = '0.0.2'
44
s.summary = 'A native iOS color picker plugin for Flutter'
55
s.description = <<-DESC
66
A Flutter plugin that provides access to the native iOS 14+ color picker.

lib/src/color_model.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ class ColorModel {
1313
this.alpha = 1.0,
1414
});
1515

16-
/// Creates a [ColorModel] from a map containing RGBA values.
17-
factory ColorModel.fromMap(Map<String, double> map) {
16+
/// Tworzy ColorModel z mapy, niezależnie od typu dynamicznego.
17+
factory ColorModel.fromMap(Map<String, dynamic> map) {
1818
return ColorModel(
19-
red: map['red'] ?? 0.0,
20-
green: map['green'] ?? 0.0,
21-
blue: map['blue'] ?? 0.0,
22-
alpha: map['alpha'] ?? 1.0,
19+
red: (map['red'] ?? 0.0).toDouble(),
20+
green: (map['green'] ?? 0.0).toDouble(),
21+
blue: (map['blue'] ?? 0.0).toDouble(),
22+
alpha: (map['alpha'] ?? 1.0).toDouble(),
2323
);
2424
}
2525

26-
/// Converts the color model to a Flutter [Color].
26+
/// Konwertuje model na Flutterowy Color.
2727
Color toColor() {
2828
return Color.fromRGBO(
2929
(red * 255).round(),
@@ -33,7 +33,7 @@ class ColorModel {
3333
);
3434
}
3535

36-
/// Converts the color model to a map representation.
36+
/// Konwertuje model z powrotem na mapę.
3737
Map<String, double> toMap() {
3838
return {
3939
'red': red,
@@ -42,4 +42,4 @@ class ColorModel {
4242
'alpha': alpha,
4343
};
4444
}
45-
}
45+
}

lib/src/color_picker.dart

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,21 @@ class NativeIosColorPicker {
55
static const MethodChannel _channel =
66
MethodChannel('native_ios_color_picker');
77

8-
/// Shows the native iOS color picker and returns the selected color.
9-
///
10-
/// Returns a [Map] containing the RGBA values of the selected color:
11-
/// - red: Double value between 0.0 and 1.0
12-
/// - green: Double value between 0.0 and 1.0
13-
/// - blue: Double value between 0.0 and 1.0
14-
/// - alpha: Double value between 0.0 and 1.0
15-
static Future<Map<String, double>> showColorPicker() async {
8+
static const EventChannel _eventChannel =
9+
EventChannel('native_ios_color_picker/events');
10+
11+
/// Shows the native macOS color picker window.
12+
/// Color changes are streamed via [onColorChanged].
13+
static Future<void> showColorPicker() async {
1614
try {
17-
final Map<dynamic, dynamic> result =
18-
await _channel.invokeMethod('showColorPicker');
19-
return {
20-
'red': result['red'] as double,
21-
'green': result['green'] as double,
22-
'blue': result['blue'] as double,
23-
'alpha': result['alpha'] as double,
24-
};
15+
await _channel.invokeMethod('showColorPicker');
2516
} on PlatformException catch (e) {
2617
throw 'Failed to show color picker: ${e.message}';
2718
}
2819
}
20+
21+
/// Live stream of RGBA color updates from native macOS picker.
22+
static Stream<Map<dynamic, dynamic>> get onColorChanged {
23+
return _eventChannel.receiveBroadcastStream().cast<Map<dynamic, dynamic>>();
24+
}
2925
}

0 commit comments

Comments
 (0)