voyage 0.0.4-canary.4
voyage: ^0.0.4-canary.4 copied to clipboard
A Flutter Plugin developed to test Kruzr Zen application.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:voyage/kruzr_360_communicator.dart';
import 'package:voyage/model/kruzr_360_init_config.dart';
import 'package:voyage/model/registered_driver.dart';
import 'package:voyage/model/single_trip_response.dart';
import 'package:voyage/model/trip_stats_response.dart';
import 'package:voyage/model/vehicle/nearby_device.dart';
// change your licence key on line 87
void main() {
runApp(const ZenApp());
}
class ZenApp extends StatelessWidget {
const ZenApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Zen App',
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: const Color(0xFF121212),
textTheme: const TextTheme(
bodyMedium: TextStyle(fontFamily: 'monospace', fontSize: 16),
),
),
home: const LoginScreen(),
debugShowCheckedModeBanner: false,
);
}
}
/* --------------------- LOGIN SCREEN --------------------- */
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _name = TextEditingController();
final TextEditingController _email = TextEditingController();
final TextEditingController _phone = TextEditingController();
// create communicator instance once and reuse
final _kruzr = Kruzr360Communicator(
companyName: "allie_flutter",
accountName: "allie_flutter",
);
bool _loading = false;
String _error = '';
Future<void> _requestPermissions() async {
await Permission.locationWhenInUse.request();
await Permission.locationAlways.request();
await Permission.activityRecognition.request();
await Permission.ignoreBatteryOptimizations.request();
await Permission.notification.request();
}
Future<void> _registerUser() async {
setState(() {
_loading = true;
_error = '';
});
await _requestPermissions(); // non-blocking
try {
final initConfig = Kruzr360InitConfig(
licenseKey: "your licence key",
appName: "Zen",
notificationChannelId: "zen_trip_channel",
shouldTripAutoStart: true,
shouldTripAutoEnd: true,
allowEventSyncRealTime: true,
);
await Kruzr360Communicator.initializeSDK(initConfig);
final int userId = await _kruzr.registerUserToKruzr(
name: _name.text.trim(),
driverId: _phone.text.trim(),
email: _email.text.trim(),
countryCode: "+91",
phoneNumber: _phone.text.trim(),
);
if (userId > 0) {
// After a successful register, navigate. Check mounted to avoid BuildContext across async gap.
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) =>
HomeScreen(kruzr: _kruzr, initialUserName: _name.text.trim()),
),
);
} else {
if (!mounted) return;
setState(() {
_error = "User registration failed (no userId returned).";
});
}
} catch (e, st) {
if (!mounted) return;
setState(() {
_error = "Error during registration: $e";
});
debugPrint(st.toString());
} finally {
if (!mounted) {
setState(() {
_loading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(24),
child: ListView(
children: [
const SizedBox(height: 100),
const Text(
'Welcome to Zen',
style: TextStyle(fontSize: 32, fontFamily: 'monospace'),
),
const SizedBox(height: 10),
const Text(
'Social Well Being Experiment',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 30),
TextField(
controller: _name,
decoration: const InputDecoration(labelText: 'Name'),
),
TextField(
controller: _email,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _phone,
decoration: const InputDecoration(labelText: 'Phone Number'),
keyboardType: TextInputType.phone,
),
const SizedBox(height: 30),
if (_error.isNotEmpty)
Text(_error, style: const TextStyle(color: Colors.red)),
ElevatedButton(
onPressed: _loading ? null : _registerUser,
child: Text(_loading ? 'Please wait...' : 'Explore'),
),
],
),
),
);
}
}
/* --------------------- HOME SCREEN --------------------- */
class HomeScreen extends StatefulWidget {
final Kruzr360Communicator kruzr;
final String initialUserName;
const HomeScreen({
super.key,
required this.kruzr,
required this.initialUserName,
});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
RegisteredDriver? _registeredDriver; // overall stats + name
SingleTripResponse? _lastTrip; // raw trip info if available
TripStatsResponse? _lastTripStats; // more stats per trip if available
bool _loading = true;
String? _error;
@override
void initState() {
super.initState();
// load after build kicked off
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadAll();
});
}
Future<void> _loadAll() async {
setState(() {
_loading = true;
_error = null;
});
try {
// 1) Try to get registered driver details (registered driver has the totals)
final dynamic driverRaw = await _safeCall(
() => widget.kruzr.userDetails(),
);
_registeredDriver = _parseRegisteredDriver(driverRaw);
// 2) Get latest trips list (plugin provides fetchTripList(offset, limit))
final dynamic tripsRaw = await _safeCall(
() => widget.kruzr.fetchTripList(0, 1),
);
final List<SingleTripResponse>? trips = _parseSingleTripList(tripsRaw);
if (trips != null && trips.isNotEmpty) {
_lastTrip = trips.first;
// 3) fetch trip stats for that trip (if the trip has appTripId)
final appTripId = _lastTrip?.appTripId;
if (appTripId != null && appTripId.isNotEmpty) {
final dynamic tripStatsRaw = await _safeCall(
() => widget.kruzr.fetchTripStatsByAppTripId(appTripId),
);
_lastTripStats = _parseTripStats(tripStatsRaw);
}
}
if (!mounted) return;
setState(() {
_loading = false;
});
} catch (e, st) {
debugPrint("load error: $e\n$st");
if (!mounted) return;
setState(() {
_error = e.toString();
_loading = false;
});
}
}
// run a plugin call and catch exceptions to avoid crashing UI
Future<dynamic> _safeCall(Future<dynamic> Function() fn) async {
try {
return await fn();
} catch (e) {
debugPrint("plugin call failed: $e");
return null;
}
}
// robust parsers: plugin might return Map, JSON-string, or model already
RegisteredDriver? _parseRegisteredDriver(dynamic raw) {
if (raw == null) return null;
try {
if (raw is RegisteredDriver) return raw;
if (raw is String) {
final Map<String, dynamic> m = jsonDecode(raw);
return RegisteredDriver.fromJson(m);
}
if (raw is Map) {
return RegisteredDriver.fromJson(Map<String, dynamic>.from(raw));
}
} catch (e) {
debugPrint("parseRegisteredDriver failed: $e");
}
return null;
}
TripStatsResponse? _parseTripStats(dynamic raw) {
if (raw == null) return null;
try {
if (raw is TripStatsResponse) return raw;
if (raw is String) {
final Map<String, dynamic> m = jsonDecode(raw);
return TripStatsResponse.fromJson(m);
}
if (raw is Map) {
return TripStatsResponse.fromJson(Map<String, dynamic>.from(raw));
}
} catch (e) {
debugPrint("parseTripStats failed: $e");
}
return null;
}
List<SingleTripResponse>? _parseSingleTripList(dynamic raw) {
if (raw == null) return null;
try {
// if it's already a list of SingleTripResponse
if (raw is List<SingleTripResponse>) return raw;
// If plugin returned JSON string array
if (raw is String) {
final decoded = jsonDecode(raw);
if (decoded is List) {
return decoded.map((e) {
if (e is SingleTripResponse) return e;
if (e is Map) {
return SingleTripResponse.fromJson(Map<String, dynamic>.from(e));
}
return SingleTripResponse.fromJson(Map<String, dynamic>.from(e));
}).toList();
}
}
// If plugin returned List<Map>
if (raw is List) {
return raw.map((e) {
if (e is SingleTripResponse) return e;
if (e is Map) {
return SingleTripResponse.fromJson(Map<String, dynamic>.from(e));
}
// fallback: try jsonEncode then parse
return SingleTripResponse.fromJson(
Map<String, dynamic>.from(jsonDecode(e)),
);
}).toList();
}
} catch (e) {
debugPrint("parseSingleTripList failed: $e");
}
return null;
}
// helpers to show safe text
String _asTextNum(num? v, {int fixed = 2}) {
if (v == null) return "-";
return (v.toDouble()).toStringAsFixed(fixed);
}
String _formatDurationFromSeconds(num? seconds) {
if (seconds == null) return "-";
final d = Duration(seconds: seconds.toInt());
String two(int n) => n.toString().padLeft(2, "0");
return "${two(d.inHours)}:${two(d.inMinutes.remainder(60))}:${two(d.inSeconds.remainder(60))}";
}
// ---------------- DEV TEST HELPERS ----------------
void _logTestResult(String label, dynamic result) {
debugPrint("๐งช [$label] => $result");
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("$label โ"),
duration: const Duration(milliseconds: 700),
),
);
}
Future<void> _runDevTest(
String label,
Future<dynamic> Function() fn,
) async {
try {
final res = await fn();
_logTestResult(label, res);
} catch (e, st) {
debugPrint("โ [$label] error: $e\n$st");
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("$label โ $e"),
backgroundColor: Colors.red,
),
);
}
}
// UI
@override
Widget build(BuildContext context) {
final displayName = _registeredDriver?.name ?? widget.initialUserName;
return Scaffold(
appBar: AppBar(
title: Text(
displayName,
style: const TextStyle(fontFamily: 'monospace'),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: _loading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(
child: Text(
"Error: $_error",
style: const TextStyle(color: Colors.red),
),
)
: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
// RegisteredDriver card with overall stats
Card(
color: const Color(0xFF1E1E1E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Trips taken: ${_registeredDriver?.totalTripCount ?? 0}",
),
Text(
"Total distance: ${_asTextNum(_registeredDriver?.totalDistanceTravelled)} km",
),
Text(
"Total duration: ${_formatDurationFromSeconds(_registeredDriver?.totalTripDurationInSeconds)}",
),
const SizedBox(height: 12),
Text(
"Driving score: ${_asTextNum(_registeredDriver?.averageTripScore, fixed: 2)}",
),
],
),
),
),
const SizedBox(height: 16),
// Last trip card (single trip + stats)
if (_lastTrip != null || _lastTripStats != null) ...[
const Text('Last trip', style: TextStyle(fontSize: 18)),
Card(
color: const Color(0xFF1E1E1E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_lastTrip != null) ...[
Text("App trip id: ${_lastTrip!.appTripId}"),
Text(
"Trip start: ${_lastTrip!.startTime ?? '-'}",
),
const SizedBox(height: 6),
],
if (_lastTripStats != null) ...[
Text(
"Score: ${_asTextNum(_lastTrip!.overallTripScore, fixed: 2)}",
),
Text(
"Distance: ${_asTextNum(_lastTrip!.totalDistance)} km",
),
Text(
"Duration: ${_lastTrip!.getTripDuration() ?? '-'} min",
),
Text(
"Status: ${_lastTrip!.tripScoringStatus?.value ?? '-'}",
),
] else
const Text("No trip stats available"),
],
),
),
),
] else
const Text("No trips found"),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () async {
// ensure permissions before starting
final granted = await [
Permission.location,
Permission.locationAlways,
Permission.activityRecognition,
Permission.ignoreBatteryOptimizations,
].request();
if (!granted.values.every((s) => s.isGranted)) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please grant required permissions"),
),
);
return;
}
try {
final started = await widget.kruzr.startTrip();
debugPrint("Trip started: $started");
// reload latest trip after a short delay if needed
await Future.delayed(const Duration(seconds: 1));
_loadAll();
} catch (e) {
debugPrint("startTrip error: $e");
}
},
child: const Text("Start Trip"),
),
ElevatedButton(
onPressed: () async {
try {
final stopped = await widget.kruzr.stopTrip();
debugPrint("Trip stopped: $stopped");
// refresh views
await Future.delayed(const Duration(milliseconds: 800));
_loadAll();
} catch (e) {
debugPrint("stopTrip error: $e");
}
},
child: const Text("Stop Trip"),
),
const SizedBox(height: 32),
const Divider(),
const Text(
"Developer Test Panel",
style: TextStyle(fontSize: 18, fontFamily: 'monospace'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () => _runDevTest(
"userDetails",
() => widget.kruzr.userDetails(),
),
child: const Text("Test: userDetails"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"getUserRank",
() => widget.kruzr.getCurrentUserRank(),
),
child: const Text("Test: getUserRank"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"getLeaderboardTop10",
() => widget.kruzr.getLeaderboardTop10(),
),
child: const Text("Test: getLeaderboardTop10"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"fetchMyAchievements",
() => widget.kruzr.fetchMyAchievements(),
),
child: const Text("Test: fetchMyAchievements"),
),
ElevatedButton(
onPressed: _lastTrip?.appTripId == null
? null
: () => _runDevTest(
"fetchRoute (GeoJSON)",
() => widget.kruzr.fetchRoute(_lastTrip!.appTripId),
),
child: const Text("Test: fetchRoute"),
),
ElevatedButton(
onPressed: _lastTrip?.appTripId == null
? null
: () => _runDevTest(
"getPossibleInterventions",
() => widget.kruzr.getPossibleInterventionsForAppTripId(
_lastTrip!.appTripId,
),
),
child: const Text("Test: getPossibleInterventions"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"scanForNearbyDevices",
() => widget.kruzr.scanForNearbyDevices(),
),
child: const Text("Test: scanForNearbyDevices"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"stopScanningForNearbyDevices",
() => widget.kruzr.stopScanningForNearbyDevices(),
),
child: const Text("Test: stopScanning"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"getAllPairedDevices",
() => widget.kruzr.getAllPairedDevices(),
),
child: const Text("Test: getAllPairedDevices"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"getConnectedDevices",
() => widget.kruzr.getConnectedDevices(),
),
child: const Text("Test: getConnectedDevices"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"saveVehicle",
() {
final device = NearbyDevice(
id: "DEV_TEST_VEHICLE",
name: "Test Car",
);
widget.kruzr.saveVehicle(device); // void method
return Future.value("OK"); // โ
wrap in Future
},
),
child: const Text("Test: saveVehicle"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"getSavedVehicle",
() => widget.kruzr.getSavedVehicle(),
),
child: const Text("Test: getSavedVehicle"),
),
ElevatedButton(
onPressed: () => _runDevTest(
"deleteVehicle",
() {
widget.kruzr.deleteVehicle(); // void method
return Future.value("OK"); // โ
wrap in Future
},
),
child: const Text("Test: deleteVehicle"),
),
],
),
),
);
}
}