voyage 0.0.3 copy "voyage: ^0.0.3" to clipboard
voyage: ^0.0.3 copied to clipboard

PlatformAndroid

Plugin to test the working of plugin already developed.

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/registered_driver.dart';
import 'package:voyage/model/single_trip_response.dart';
import 'package:voyage/model/trip_stats_response.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<bool> _requestPermissions() async {
    final statuses = await [
      Permission.location,
      Permission.locationAlways,
      Permission.activityRecognition,
      Permission.ignoreBatteryOptimizations,
      Permission.notification,
      // add bluetooth perms if you need them: Permission.bluetooth, Permission.bluetoothScan, etc
    ].request();

    // return whether all required permissions are granted
    return statuses.values.every((s) => s.isGranted);
  }

  Future<void> _registerUser() async {
    setState(() {
      _loading = true;
      _error = '';
    });

    // do permission request first — avoid calling SDK before required permissions
    final permsOk = await _requestPermissions();
    if (!permsOk) {
      if (!mounted) return;
      setState(() {
        _loading = false;
        _error = "Required permissions not granted. Please allow location/background access.";
      });
      return;
    }

    try {
      const licenseKey = "your licence key"; // replace with actual
      await Kruzr360Communicator.initializeSDK(licenseKey);

      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))}";
  }

  // 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)) {
                  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"),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
140
points
1.37k
downloads

Publisher

unverified uploader

Weekly Downloads

Plugin to test the working of plugin already developed.

Homepage
Repository (GitHub)

Documentation

API reference

License

unknown (license)

Dependencies

flutter, geojson_vi, permission_handler, plugin_platform_interface

More

Packages that depend on voyage

Packages that implement voyage