voyage 0.0.4-canary.2 copy "voyage: ^0.0.4-canary.2" to clipboard
voyage: ^0.0.4-canary.2 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"),
                  ),
                ],
              ),
            ),
    );
  }
}
0
likes
0
points
1.65k
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter Plugin developed to test Kruzr Zen application.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, geojson_vi, permission_handler, plugin_platform_interface

More

Packages that depend on voyage

Packages that implement voyage