voyage 0.0.3-canary.7 copy "voyage: ^0.0.3-canary.7" to clipboard
voyage: ^0.0.3-canary.7 copied to clipboard

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';

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
0
points
1.63k
downloads

Publisher

unverified uploader

Weekly Downloads

Plugin to test the working of plugin already developed.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, permission_handler, plugin_platform_interface

More

Packages that depend on voyage

Packages that implement voyage