Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

## 🧐 What is it ?

Gaimon is a **very simple** & **easy to use** plugin to include **Haptic feedback** in your app. It support custom pattern with `.ahap` file support.
Gaimon is a **very simple** & **easy to use** plugin to include **Haptic feedback** in your app. It supports custom patterns with `.ahap` file support.

While the custom haptic patterns and native actions are implemented for **iOS** and **Android**, Gaimon is safe to compile and use on **any platform** (Web, macOS, Windows, Linux, etc.). On unsupported platforms, it degrades gracefully by failing silently (no-op) and returning `false` for `canSupportsHaptic`.
<br />

<p align="center">
Expand Down
8 changes: 4 additions & 4 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.18.0"
path:
dependency: transitive
description:
Expand Down Expand Up @@ -195,10 +195,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.11"
vector_math:
dependency: transitive
description:
Expand Down
67 changes: 52 additions & 15 deletions lib/gaimon.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/services.dart';
import 'package:gaimon/ahap_to_waveform_converter.dart';

class Gaimon {
static const MethodChannel _channel = MethodChannel('gaimon');

static bool get _isAndroid => !kIsWeb && Platform.isAndroid;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is enough, same for iOS

Suggested change
static bool get _isAndroid => !kIsWeb && Platform.isAndroid;
static bool get _isAndroid => Platform.isAndroid;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @istornz I'm not sure I'm following - web doesn't support the Platform API - so why wouldn't we add this before a native platform check?

static bool get _isIOS => !kIsWeb && Platform.isIOS;
static bool get _supportsMethodChannel => _isAndroid || _isIOS;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe adding the kIsWeb in this condition is better?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@istornz This package only supports Android and iOS, so I'm not sure why adding a kIsWeb would be useful here.


/// check if the device can vibrate or not
static Future<bool> get canSupportsHaptic async {
return await _channel.invokeMethod('canSupportsHaptic');
if (!_supportsMethodChannel) return false;
try {
return await _channel.invokeMethod<bool>('canSupportsHaptic') ?? false;
} catch (_) {
return false;
}
}

/// generate a selection impact vibration
static void selection() => HapticFeedback.selectionClick();

/// generate an error impact vibration
static void error() => _channel.invokeMethod('error');
static void error() {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('error');
}

/// generate a success impact vibration
static void success() => _channel.invokeMethod('success');
static void success() {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('success');
}

/// generate a warning impact vibration
static void warning() => _channel.invokeMethod('warning');
static void warning() {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('warning');
}

/// generate a heavy impact vibration
static void heavy() => HapticFeedback.heavyImpact();
Expand All @@ -34,32 +53,50 @@ class Gaimon {
static void light() => HapticFeedback.lightImpact();

/// generate a rigid impact vibration
static void rigid() => _channel.invokeMethod('rigid');
static void rigid() {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('rigid');
}

/// generate a soft impact vibration
static void soft() => _channel.invokeMethod('soft');
static void soft() {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('soft');
}

/// generate a custom pattern impact vibration
static void patternFromData(String data) => Platform.isAndroid
? _patternFromAhapToWaveform(data)
: _channel.invokeMethod('pattern', {'data': data});
static void patternFromData(String data) {
if (!_supportsMethodChannel) return;
if (_isAndroid) {
_patternFromAhapToWaveform(data);
} else {
_channel.invokeMethod('pattern', {'data': data});
}
}

static void _patternFromAhapToWaveform(String data) {
final waveform = ahapToWaveform(data);
patternFromWaveForm(waveform.timings, waveform.amplitudes, waveform.repeat);
}

/// stop any ongoing vibration
static void stop() => _channel.invokeMethod('stop');
static void stop() {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('stop');
}

/// generate a custom pattern impact vibration from waveform (android only)
static void patternFromWaveForm(
List<int> timings,
List<int> amplitudes,
bool repeat,
) => _channel.invokeMethod('pattern', {
'timings': timings,
'amplitudes': amplitudes,
'repeat': repeat,
});
) {
if (!_supportsMethodChannel) return;
_channel.invokeMethod('pattern', {
'timings': timings,
'amplitudes': amplitudes,
'repeat': repeat,
});
}
}

28 changes: 28 additions & 0 deletions test/gaimon_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:gaimon/gaimon.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

group('Gaimon Multi-platform Safety Tests', () {
test('canSupportsHaptic returns false on unsupported platform (macOS/test environment)', () async {
final supportsHaptic = await Gaimon.canSupportsHaptic;
expect(supportsHaptic, isFalse);
});

test('calling haptic actions does not throw on unsupported platforms', () {
expect(() => Gaimon.selection(), returnsNormally);
expect(() => Gaimon.error(), returnsNormally);
expect(() => Gaimon.success(), returnsNormally);
expect(() => Gaimon.warning(), returnsNormally);
expect(() => Gaimon.heavy(), returnsNormally);
expect(() => Gaimon.medium(), returnsNormally);
expect(() => Gaimon.light(), returnsNormally);
expect(() => Gaimon.rigid(), returnsNormally);
expect(() => Gaimon.soft(), returnsNormally);
expect(() => Gaimon.patternFromData('{}'), returnsNormally);
expect(() => Gaimon.patternFromWaveForm([100], [128], false), returnsNormally);
expect(() => Gaimon.stop(), returnsNormally);
});
});
}