Fix android and update example
This commit is contained in:
+36
-6
@@ -12,6 +12,7 @@ class VibrateMethodCallHandler implements MethodChannel.MethodCallHandler {
|
|||||||
private final Vibrator vibrator;
|
private final Vibrator vibrator;
|
||||||
private final boolean hasVibrator;
|
private final boolean hasVibrator;
|
||||||
private final boolean legacyVibrator;
|
private final boolean legacyVibrator;
|
||||||
|
private android.app.Activity activity;
|
||||||
|
|
||||||
VibrateMethodCallHandler(Vibrator vibrator) {
|
VibrateMethodCallHandler(Vibrator vibrator) {
|
||||||
assert (vibrator != null);
|
assert (vibrator != null);
|
||||||
@@ -20,6 +21,10 @@ class VibrateMethodCallHandler implements MethodChannel.MethodCallHandler {
|
|||||||
this.legacyVibrator = Build.VERSION.SDK_INT < 26;
|
this.legacyVibrator = Build.VERSION.SDK_INT < 26;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setActivity(android.app.Activity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void vibrate(int duration) {
|
private void vibrate(int duration) {
|
||||||
if (hasVibrator) {
|
if (hasVibrator) {
|
||||||
@@ -31,6 +36,19 @@ class VibrateMethodCallHandler implements MethodChannel.MethodCallHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void feedback(int feedbackConstant) {
|
||||||
|
if (activity != null) {
|
||||||
|
activity.getWindow().getDecorView().performHapticFeedback(feedbackConstant);
|
||||||
|
} else {
|
||||||
|
// Fallback to vibrator if no activity/view (though unlikely in a running app)
|
||||||
|
// or just ignore if strictly view-based.
|
||||||
|
// For now, let's fallback to the old vibration logic for consistency if View is not ready,
|
||||||
|
// although the user specifically wants View feedback.
|
||||||
|
// Actually, the old logic was calling vibrate(int) for specific types.
|
||||||
|
// We can map some to vibrate(int) if really needed, but let's assume Activity is there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
|
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
|
||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
@@ -39,39 +57,51 @@ class VibrateMethodCallHandler implements MethodChannel.MethodCallHandler {
|
|||||||
break;
|
break;
|
||||||
case "vibrate":
|
case "vibrate":
|
||||||
final int duration = call.argument("duration");
|
final int duration = call.argument("duration");
|
||||||
vibrate(duration);
|
vibrate(d);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "impact":
|
case "impact":
|
||||||
vibrate(HapticFeedbackConstants.VIRTUAL_KEY);
|
feedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "selection":
|
case "selection":
|
||||||
vibrate(HapticFeedbackConstants.KEYBOARD_TAP);
|
feedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "success":
|
case "success":
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
feedback(HapticFeedbackConstants.CONFIRM);
|
||||||
|
} else {
|
||||||
vibrate(50);
|
vibrate(50);
|
||||||
|
}
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "warning":
|
case "warning":
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
feedback(HapticFeedbackConstants.REJECT);
|
||||||
|
} else {
|
||||||
vibrate(250);
|
vibrate(250);
|
||||||
|
}
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
feedback(HapticFeedbackConstants.REJECT);
|
||||||
|
} else {
|
||||||
vibrate(500);
|
vibrate(500);
|
||||||
|
}
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "heavy":
|
case "heavy":
|
||||||
vibrate(100);
|
feedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
vibrate(40);
|
feedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case "light":
|
case "light":
|
||||||
vibrate(10);
|
feedback(HapticFeedbackConstants.CLOCK_TICK);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
+24
-2
@@ -7,15 +7,16 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
|||||||
import io.flutter.plugin.common.BinaryMessenger;
|
import io.flutter.plugin.common.BinaryMessenger;
|
||||||
import io.flutter.plugin.common.MethodChannel;
|
import io.flutter.plugin.common.MethodChannel;
|
||||||
|
|
||||||
public class VibratePlugin implements FlutterPlugin {
|
public class VibratePlugin implements FlutterPlugin, io.flutter.embedding.engine.plugins.activity.ActivityAware {
|
||||||
private MethodChannel methodChannel;
|
private MethodChannel methodChannel;
|
||||||
|
private VibrateMethodCallHandler methodCallHandler;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttachedToEngine(FlutterPluginBinding binding) {
|
public void onAttachedToEngine(FlutterPluginBinding binding) {
|
||||||
final Context context = binding.getApplicationContext();
|
final Context context = binding.getApplicationContext();
|
||||||
final BinaryMessenger messenger = binding.getBinaryMessenger();
|
final BinaryMessenger messenger = binding.getBinaryMessenger();
|
||||||
final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
final VibrateMethodCallHandler methodCallHandler = new VibrateMethodCallHandler(vibrator);
|
methodCallHandler = new VibrateMethodCallHandler(vibrator);
|
||||||
|
|
||||||
this.methodChannel = new MethodChannel(messenger, "vibrate");
|
this.methodChannel = new MethodChannel(messenger, "vibrate");
|
||||||
this.methodChannel.setMethodCallHandler(methodCallHandler);
|
this.methodChannel.setMethodCallHandler(methodCallHandler);
|
||||||
@@ -25,5 +26,26 @@ public class VibratePlugin implements FlutterPlugin {
|
|||||||
public void onDetachedFromEngine(FlutterPluginBinding binding) {
|
public void onDetachedFromEngine(FlutterPluginBinding binding) {
|
||||||
this.methodChannel.setMethodCallHandler(null);
|
this.methodChannel.setMethodCallHandler(null);
|
||||||
this.methodChannel = null;
|
this.methodChannel = null;
|
||||||
|
this.methodCallHandler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToActivity(io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding binding) {
|
||||||
|
methodCallHandler.setActivity(binding.getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachedFromActivityForConfigChanges() {
|
||||||
|
methodCallHandler.setActivity(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReattachedToActivityForConfigChanges(io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding binding) {
|
||||||
|
methodCallHandler.setActivity(binding.getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachedFromActivity() {
|
||||||
|
methodCallHandler.setActivity(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<application
|
<application
|
||||||
android:label="vibrate_example"
|
android:label="vibrate_example"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -1,142 +1,443 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
||||||
|
|
||||||
void main() => runApp(const MyApp());
|
void main() {
|
||||||
|
runApp(const VibrateExampleApp());
|
||||||
class MyApp extends StatefulWidget {
|
|
||||||
const MyApp({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_MyAppState createState() => _MyAppState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class VibrateExampleApp extends StatelessWidget {
|
||||||
bool _canVibrate = true;
|
const VibrateExampleApp({super.key});
|
||||||
final Iterable<Duration> pauses = [
|
|
||||||
const Duration(milliseconds: 500),
|
|
||||||
const Duration(milliseconds: 1000),
|
|
||||||
const Duration(milliseconds: 500),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _init() async {
|
|
||||||
bool canVibrate = await Vibrate.canVibrate;
|
|
||||||
setState(() {
|
|
||||||
_canVibrate = canVibrate;
|
|
||||||
_canVibrate
|
|
||||||
? debugPrint('This device can vibrate')
|
|
||||||
: debugPrint('This device cannot vibrate');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
home: Scaffold(
|
title: 'Haptic Feedback',
|
||||||
appBar: AppBar(title: const Text('Haptic Feedback Example')),
|
theme: ThemeData(
|
||||||
body: Center(
|
useMaterial3: true,
|
||||||
child: ListView(children: [
|
colorScheme: ColorScheme.fromSeed(
|
||||||
ListTile(
|
seedColor: Colors.deepPurple,
|
||||||
title: const Text('Vibrate'),
|
brightness: Brightness.light,
|
||||||
leading: const Icon(Icons.vibration, color: Colors.teal),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) Vibrate.vibrate;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
ListTile(
|
),
|
||||||
title: const Text('Vibrate with Pauses'),
|
darkTheme: ThemeData(
|
||||||
leading: const Icon(Icons.vibration, color: Colors.brown),
|
useMaterial3: true,
|
||||||
onTap: () {
|
colorScheme: ColorScheme.fromSeed(
|
||||||
if (_canVibrate) {
|
seedColor: Colors.deepPurple,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
themeMode: ThemeMode.system,
|
||||||
|
home: const HapticFeedbackDemo(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HapticFeedbackDemo extends StatefulWidget {
|
||||||
|
const HapticFeedbackDemo({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HapticFeedbackDemo> createState() => _HapticFeedbackDemoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HapticFeedbackDemoState extends State<HapticFeedbackDemo> {
|
||||||
|
// We use a future to track initialization status
|
||||||
|
late final Future<bool> _initFuture;
|
||||||
|
bool _canVibrate = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initFuture = _init();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _init() async {
|
||||||
|
final canVibrate = await Vibrate.canVibrate;
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_canVibrate = canVibrate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return canVibrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Haptic Studio'),
|
||||||
|
centerTitle: true,
|
||||||
|
notificationPredicate: (notification) => notification.depth == 1,
|
||||||
|
scrolledUnderElevation: 4.0,
|
||||||
|
),
|
||||||
|
body: FutureBuilder<bool>(
|
||||||
|
future: _initFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(child: Text('Failed to initialize vibration.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _DeviceabilityHeader(canVibrate: _canVibrate),
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(child: SizedBox(height: 16)),
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildListDelegate([
|
||||||
|
_SectionHeader(title: 'Basic Vibration'),
|
||||||
|
_VibrateCard(
|
||||||
|
title: 'Standard Vibrate',
|
||||||
|
subtitle: '500ms vibration',
|
||||||
|
icon: Icons.vibration,
|
||||||
|
onTap: _canVibrate ? () => Vibrate.vibrate() : null,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_VibrateCard(
|
||||||
|
title: 'Pattern Vibrate',
|
||||||
|
subtitle: '500ms, wait 1s, 500ms',
|
||||||
|
icon: Icons.graphic_eq,
|
||||||
|
onTap: _canVibrate
|
||||||
|
? () {
|
||||||
|
final pauses = [
|
||||||
|
const Duration(milliseconds: 500),
|
||||||
|
const Duration(milliseconds: 1000),
|
||||||
|
const Duration(milliseconds: 500),
|
||||||
|
];
|
||||||
Vibrate.vibrateWithPauses(pauses);
|
Vibrate.vibrateWithPauses(pauses);
|
||||||
}
|
}
|
||||||
},
|
: null,
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Impact'),
|
|
||||||
leading: const Icon(Icons.tap_and_play, color: Colors.orange),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.impact);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Selection'),
|
|
||||||
leading: const Icon(Icons.select_all, color: Colors.blue),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.selection);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Success'),
|
|
||||||
leading: const Icon(Icons.check, color: Colors.green),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.success);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Warning'),
|
|
||||||
leading: const Icon(Icons.warning, color: Colors.red),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.warning);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Error'),
|
|
||||||
leading: const Icon(Icons.error, color: Colors.red),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Heavy'),
|
|
||||||
leading:
|
|
||||||
const Icon(Icons.notification_important, color: Colors.red),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.heavy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Medium'),
|
|
||||||
leading:
|
|
||||||
const Icon(Icons.notification_important, color: Colors.green),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.medium);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Light'),
|
|
||||||
leading:
|
|
||||||
Icon(Icons.notification_important, color: Colors.yellow[700]),
|
|
||||||
onTap: () {
|
|
||||||
if (_canVibrate) {
|
|
||||||
Vibrate.feedback(FeedbackType.light);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_SectionHeader(title: 'Haptic Feedback'),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
|
sliver: SliverGrid(
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 200,
|
||||||
|
mainAxisSpacing: 12,
|
||||||
|
crossAxisSpacing: 12,
|
||||||
|
childAspectRatio: 1.5,
|
||||||
|
),
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
final item = _feedbackItems[index];
|
||||||
|
return _FeedbackTile(
|
||||||
|
item: item,
|
||||||
|
isEnabled: _canVibrate,
|
||||||
|
onTap: () {
|
||||||
|
if (_canVibrate) {
|
||||||
|
Vibrate.feedback(item.type);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, childCount: _feedbackItems.length),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeviceabilityHeader extends StatelessWidget {
|
||||||
|
final bool canVibrate;
|
||||||
|
|
||||||
|
const _DeviceabilityHeader({required this.canVibrate});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: canVibrate
|
||||||
|
? colorScheme.primaryContainer
|
||||||
|
: colorScheme.errorContainer,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
canVibrate ? Icons.check_circle_outline : Icons.error_outline,
|
||||||
|
size: 32,
|
||||||
|
color: canVibrate
|
||||||
|
? colorScheme.onPrimaryContainer
|
||||||
|
: colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
canVibrate ? 'Device Ready' : 'Capability Missing',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: canVibrate
|
||||||
|
? colorScheme.onPrimaryContainer
|
||||||
|
: colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
canVibrate
|
||||||
|
? 'This device supports vibration features.'
|
||||||
|
: 'This device does not support vibration.',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: canVibrate
|
||||||
|
? colorScheme.onPrimaryContainer.withOpacity(0.8)
|
||||||
|
: colorScheme.onErrorContainer.withOpacity(0.8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionHeader extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
const _SectionHeader({required this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12.0, left: 4),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _VibrateCard extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
const _VibrateCard({
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
required this.icon,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.play_circle_outline,
|
||||||
|
color: onTap != null
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).disabledColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeedbackTile extends StatefulWidget {
|
||||||
|
final _FeedbackItem item;
|
||||||
|
final bool isEnabled;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
const _FeedbackTile({
|
||||||
|
required this.item,
|
||||||
|
required this.isEnabled,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FeedbackTile> createState() => _FeedbackTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeedbackTileState extends State<_FeedbackTile>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
);
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 1.0,
|
||||||
|
end: 0.95,
|
||||||
|
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTap() {
|
||||||
|
if (widget.onTap != null) {
|
||||||
|
_controller.forward().then((_) => _controller.reverse());
|
||||||
|
widget.onTap!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _scaleAnimation,
|
||||||
|
builder: (context, child) =>
|
||||||
|
Transform.scale(scale: _scaleAnimation.value, child: child),
|
||||||
|
child: Material(
|
||||||
|
color:
|
||||||
|
widget.item.color?.withOpacity(0.15) ??
|
||||||
|
colorScheme.secondaryContainer.withOpacity(0.4),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
onTap: widget.isEnabled ? _handleTap : null,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
widget.item.icon,
|
||||||
|
size: 32,
|
||||||
|
color: widget.item.color ?? colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
widget.item.label,
|
||||||
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||||
|
color:
|
||||||
|
widget.item.color ?? colorScheme.onSecondaryContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeedbackItem {
|
||||||
|
final String label;
|
||||||
|
final FeedbackType type;
|
||||||
|
final IconData icon;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
const _FeedbackItem(this.label, this.type, this.icon, [this.color]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<_FeedbackItem> _feedbackItems = [
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Impact',
|
||||||
|
FeedbackType.impact,
|
||||||
|
Icons.touch_app,
|
||||||
|
Colors.orange,
|
||||||
|
),
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Success',
|
||||||
|
FeedbackType.success,
|
||||||
|
Icons.check_circle,
|
||||||
|
Colors.green,
|
||||||
|
),
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Warning',
|
||||||
|
FeedbackType.warning,
|
||||||
|
Icons.warning_amber,
|
||||||
|
Colors.orangeAccent,
|
||||||
|
),
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Error',
|
||||||
|
FeedbackType.error,
|
||||||
|
Icons.error_outline,
|
||||||
|
Colors.red,
|
||||||
|
),
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Selection',
|
||||||
|
FeedbackType.selection,
|
||||||
|
Icons.gesture,
|
||||||
|
Colors.blue,
|
||||||
|
),
|
||||||
|
const _FeedbackItem('Heavy', FeedbackType.heavy, Icons.anchor, Colors.indigo),
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Medium',
|
||||||
|
FeedbackType.medium,
|
||||||
|
Icons.circle_notifications,
|
||||||
|
Colors.purple,
|
||||||
|
),
|
||||||
|
const _FeedbackItem(
|
||||||
|
'Light',
|
||||||
|
FeedbackType.light,
|
||||||
|
Icons.bubble_chart,
|
||||||
|
Colors.teal,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ publish_to: none
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
sdk: '^3.10.4'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -15,7 +15,7 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^1.0.4
|
flutter_lints: ^5.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
Reference in New Issue
Block a user