2026-02-20-overlay
This commit is contained in:
168
trip_overlay_plugin/lib/trip_overlay_plugin.dart
Normal file
168
trip_overlay_plugin/lib/trip_overlay_plugin.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Model for trip data passed to the overlay
|
||||
class TripData {
|
||||
final String tripId;
|
||||
final String passengerName;
|
||||
final String pickupAddress;
|
||||
final String dropoffAddress;
|
||||
final double distanceKm;
|
||||
final double estimatedFare;
|
||||
final int estimatedMinutes;
|
||||
final double pickupLat;
|
||||
final double pickupLng;
|
||||
final String? passengerAvatarUrl;
|
||||
|
||||
TripData({
|
||||
required this.tripId,
|
||||
required this.passengerName,
|
||||
required this.pickupAddress,
|
||||
required this.dropoffAddress,
|
||||
required this.distanceKm,
|
||||
required this.estimatedFare,
|
||||
required this.estimatedMinutes,
|
||||
required this.pickupLat,
|
||||
required this.pickupLng,
|
||||
this.passengerAvatarUrl,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
'tripId': tripId,
|
||||
'passengerName': passengerName,
|
||||
'pickupAddress': pickupAddress,
|
||||
'dropoffAddress': dropoffAddress,
|
||||
'distanceKm': distanceKm,
|
||||
'estimatedFare': estimatedFare,
|
||||
'estimatedMinutes': estimatedMinutes,
|
||||
'pickupLat': pickupLat,
|
||||
'pickupLng': pickupLng,
|
||||
'passengerAvatarUrl': passengerAvatarUrl ?? '',
|
||||
};
|
||||
|
||||
factory TripData.fromMap(Map<String, dynamic> map) => TripData(
|
||||
tripId: map['tripId'] ?? '',
|
||||
passengerName: map['passengerName'] ?? '',
|
||||
pickupAddress: map['pickupAddress'] ?? '',
|
||||
dropoffAddress: map['dropoffAddress'] ?? '',
|
||||
distanceKm: (map['distanceKm'] ?? 0.0).toDouble(),
|
||||
estimatedFare: (map['estimatedFare'] ?? 0.0).toDouble(),
|
||||
estimatedMinutes: map['estimatedMinutes'] ?? 0,
|
||||
pickupLat: (map['pickupLat'] ?? 0.0).toDouble(),
|
||||
pickupLng: (map['pickupLng'] ?? 0.0).toDouble(),
|
||||
passengerAvatarUrl: map['passengerAvatarUrl'],
|
||||
);
|
||||
|
||||
factory TripData.fromJson(String json) =>
|
||||
TripData.fromMap(jsonDecode(json) as Map<String, dynamic>);
|
||||
|
||||
String toJson() => jsonEncode(toMap());
|
||||
}
|
||||
|
||||
/// Result returned when the driver accepts a trip
|
||||
class TripAcceptedResult {
|
||||
final String tripId;
|
||||
final DateTime acceptedAt;
|
||||
|
||||
TripAcceptedResult({required this.tripId, required this.acceptedAt});
|
||||
}
|
||||
|
||||
/// Main plugin class — single entry point for Flutter side
|
||||
class TripOverlayPlugin {
|
||||
static const MethodChannel _channel = MethodChannel('trip_overlay_plugin');
|
||||
|
||||
// Stream controller for trip accepted events coming FROM Android overlay
|
||||
static final StreamController<TripAcceptedResult> _tripAcceptedController =
|
||||
StreamController<TripAcceptedResult>.broadcast();
|
||||
|
||||
// Stream controller for trip rejected/expired events
|
||||
static final StreamController<String> _tripRejectedController =
|
||||
StreamController<String>.broadcast();
|
||||
|
||||
static bool _isInitialized = false;
|
||||
|
||||
/// Stream that fires when the driver taps "Accept" in the overlay
|
||||
static Stream<TripAcceptedResult> get onTripAccepted =>
|
||||
_tripAcceptedController.stream;
|
||||
|
||||
/// Stream that fires when the driver rejects or overlay times out
|
||||
static Stream<String> get onTripRejected => _tripRejectedController.stream;
|
||||
|
||||
/// Initialize the plugin — call this once in main() or initState()
|
||||
static Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
_channel.setMethodCallHandler(_handleMethodCall);
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// Handle incoming calls FROM Android → Flutter
|
||||
static Future<dynamic> _handleMethodCall(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case 'onTripAccepted':
|
||||
final tripId = call.arguments['tripId'] as String;
|
||||
_tripAcceptedController.add(
|
||||
TripAcceptedResult(tripId: tripId, acceptedAt: DateTime.now()),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'onTripRejected':
|
||||
final tripId = call.arguments['tripId'] as String;
|
||||
_tripRejectedController.add(tripId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw PlatformException(
|
||||
code: 'UNKNOWN_METHOD',
|
||||
message: 'Method ${call.method} not implemented',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if SYSTEM_ALERT_WINDOW permission is granted
|
||||
static Future<bool> isPermissionGranted() async {
|
||||
final result = await _channel.invokeMethod<bool>('isPermissionGranted');
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// Open system settings to grant SYSTEM_ALERT_WINDOW permission
|
||||
static Future<void> requestPermission() async {
|
||||
await _channel.invokeMethod('requestPermission');
|
||||
}
|
||||
|
||||
/// Show the trip overlay with the given [tripData]
|
||||
/// [autoCloseSeconds] — how long before auto-dismiss (default 30s)
|
||||
static Future<bool> showOverlay(
|
||||
TripData tripData, {
|
||||
int autoCloseSeconds = 30,
|
||||
}) async {
|
||||
final granted = await isPermissionGranted();
|
||||
if (!granted) {
|
||||
await requestPermission();
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await _channel.invokeMethod<bool>('showOverlay', {
|
||||
'tripData': tripData.toJson(),
|
||||
'autoCloseSeconds': autoCloseSeconds,
|
||||
});
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// Programmatically close the overlay (e.g. if trip was cancelled)
|
||||
static Future<void> hideOverlay() async {
|
||||
await _channel.invokeMethod('hideOverlay');
|
||||
}
|
||||
|
||||
/// Check if the overlay is currently visible
|
||||
static Future<bool> isOverlayActive() async {
|
||||
final result = await _channel.invokeMethod<bool>('isOverlayActive');
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// Dispose streams — call in app's dispose()
|
||||
static void dispose() {
|
||||
_tripAcceptedController.close();
|
||||
_tripRejectedController.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'trip_overlay_plugin_platform_interface.dart';
|
||||
|
||||
/// An implementation of [TripOverlayPluginPlatform] that uses method channels.
|
||||
class MethodChannelTripOverlayPlugin extends TripOverlayPluginPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('trip_overlay_plugin');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'trip_overlay_plugin_method_channel.dart';
|
||||
|
||||
abstract class TripOverlayPluginPlatform extends PlatformInterface {
|
||||
/// Constructs a TripOverlayPluginPlatform.
|
||||
TripOverlayPluginPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static TripOverlayPluginPlatform _instance = MethodChannelTripOverlayPlugin();
|
||||
|
||||
/// The default instance of [TripOverlayPluginPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelTripOverlayPlugin].
|
||||
static TripOverlayPluginPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [TripOverlayPluginPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(TripOverlayPluginPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user