initial commit: push everything

This commit is contained in:
2026-07-06 20:27:53 +05:30
commit df4e044d74
329 changed files with 36620 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart';
import 'package:http/io_client.dart';
import 'package:flutter/foundation.dart';
// Combined Riderlog providers:
/// Hardcoded known-good IPs for hosts where carrier DNS returns broken nodes.
/// Confirmed by Python test: 66.116.225.226 = 200 OK, 125.21.240.67 = 404.
const _knownGoodIPs = <String, String>{
'queue.workolik.com': '66.116.225.226',
};
/// Creates an IOClient that:
/// 1. Bypasses SSL certificate errors
/// 2. Forces known-good IPs for hosts where carrier DNS returns broken CDN nodes
/// 3. Manually does TLS upgrade with correct SNI (hostname, not IP)
IOClient _buildSslBypassClient() {
final httpClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
httpClient.connectionFactory =
(Uri uri, String? proxyHost, int? proxyPort) async {
final host = uri.host;
final port = uri.port;
// Use known-good IP if available, else resolve normally (prefer IPv4)
InternetAddress? target;
final knownIP = _knownGoodIPs[host];
if (knownIP != null) {
target = InternetAddress(knownIP);
debugPrint('[SSL_CLIENT] Using known-good IP: $knownIP for $host');
} else {
try {
final addresses = await InternetAddress.lookup(
host,
type: InternetAddressType.IPv4,
);
if (addresses.isNotEmpty) target = addresses.first;
} catch (_) {}
}
if (uri.scheme == 'https') {
final socketFuture =
Socket.connect(target ?? InternetAddress(host), port)
.then((plain) => SecureSocket.secure(
plain,
host: host, // SNI = original hostname for Nginx routing
onBadCertificate: (_) => true,
supportedProtocols: ['http/1.1'],
))
.then((s) => s as Socket);
return ConnectionTask.fromSocket<Socket>(socketFuture, () {});
}
return Socket.startConnect(target ?? InternetAddress(host), port);
};
return IOClient(httpClient);
}
class CreateRiderLogProvider {
Future<Map<String, dynamic>?> createRiderLog(
String urldata,
Map<String, dynamic> data,
) async {
const maxAttempts = 3;
try {
debugPrint('createRiderLog payload ${json.encode(data)}');
} catch (_) {}
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final response = await client.post(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
debugPrint('createRiderLog url $urldata (attempt $attempt)');
debugPrint('createRiderLog status ${response.statusCode}');
debugPrint('createRiderLog response ${response.body}');
if (response.statusCode >= 200 && response.statusCode < 300) {
return json.decode(response.body.toString()) as Map<String, dynamic>;
} else {
debugPrint(
'createRiderLog failed: HTTP ${response.statusCode} (attempt $attempt/$maxAttempts)',
);
// On 404/5xx, wait and retry to potentially hit a different CDN node
if (attempt < maxAttempts) {
await Future.delayed(const Duration(seconds: 1));
}
}
} catch (e) {
debugPrint('createRiderLog exception (attempt $attempt): $e');
if (attempt < maxAttempts) {
await Future.delayed(const Duration(seconds: 1));
}
} finally {
client.close();
}
}
debugPrint('createRiderLog failed after $maxAttempts attempts');
return null;
}
}
class UpdateRiderLogProvider {
Future<Map<String, dynamic>?> updateRiderLog(
String urldata,
Map<String, dynamic> data,
) async {
try {
final url = Uri.parse(urldata);
final response = await put(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
debugPrint('updateRiderLog url: $urldata');
debugPrint('updateRiderLog response: ${response.body}');
if (response.statusCode >= 200 && response.statusCode < 300) {
final decoded = json.decode(response.body);
if (decoded is Map<String, dynamic>) {
return decoded;
} else {
debugPrint('⚠️ updateRiderLog: Expected Map but got ${decoded.runtimeType}');
return {};
}
} else {
debugPrint('❌ updateRiderLog failed with code ${response.statusCode}');
return {};
}
} catch (e) {
debugPrint('❌ Exception in updateRiderLog: $e');
return {};
}
}
}
class GetRiderLogProvider {
Future<Map<String, dynamic>?> getRiderLog(String urldata) async {
Map<String, dynamic>? getRiderLogResponse;
try {
final url = Uri.parse(urldata);
final response = await get(url, headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
});
debugPrint('getRiderLog response ${response.body}');
debugPrint('getRiderLog url ${urldata.toString()}');
getRiderLogResponse =
json.decode(response.body.toString()) as Map<String, dynamic>;
} catch (e) {
debugPrint(e.toString());
}
return getRiderLogResponse;
}
Future<Map<String, dynamic>?> getRiderCount(String urldata) async {
Map<String, dynamic>? getRiderCountResponse;
try {
final url = Uri.parse(urldata);
final response = await get(url, headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
});
debugPrint('getRiderCount response ${response.body}');
debugPrint('getRiderCount url ${urldata.toString()}');
getRiderCountResponse =
json.decode(response.body.toString()) as Map<String, dynamic>;
} catch (e) {
debugPrint(e.toString());
}
return getRiderCountResponse;
}
}
class BreakRiderLogProvider {
Future<Map<String, dynamic>?> createBreakRiderLog(
String urldata,
Map<String, dynamic> data,
) async {
Map<String, dynamic>? breakLogResponse;
try {
final url = Uri.parse(urldata);
final response = await post(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
debugPrint('createBreakRiderLog url $urldata');
debugPrint('createBreakRiderLog response ${response.body}');
breakLogResponse =
json.decode(response.body.toString()) as Map<String, dynamic>;
} catch (e) {
debugPrint(e.toString());
}
return breakLogResponse;
}
Future<Map<String, dynamic>?> updateBreakRiderLog(
String urldata,
Map<String, dynamic> data,
) async {
Map<String, dynamic>? breakLogResponse;
try {
final url = Uri.parse(urldata);
final response = await put(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
debugPrint('updateBreakRiderLog url $urldata');
debugPrint('updateBreakRiderLog response ${response.body}');
breakLogResponse =
json.decode(response.body.toString()) as Map<String, dynamic>;
} catch (e) {
debugPrint(e.toString());
}
return breakLogResponse;
}
}

View File

@@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:nearle/Models/login/login.dart';
import 'package:shared_preferences/shared_preferences.dart';
// ignore: unused_import
import 'package:nearle/controllers/riderlog.dart';
class AuthProvider {
Future<http.Response> login({
required String contactNo,
required String deviceType,
required int configId,
required String deviceId,
required String fcmToken,
int? pin,
}) async {
final uri = Uri.parse(
'https://jupiter.nearle.app/live/api/v2/users/rider/login',
);
final body = {
'contactno': contactNo,
'devicetype': deviceType,
'configid': configId,
'deviceid': deviceId,
'userfcmtoken': fcmToken,
if (pin != null) 'pin': pin,
};
debugPrint('[AUTH][LOGIN] URL: ${uri.toString()}');
debugPrint('[AUTH][LOGIN] Body: ${json.encode(body)}');
final res = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
debugPrint('[AUTH][LOGIN] Status: ${res.statusCode}');
debugPrint('[AUTH][LOGIN] Response: ${res.body}');
return res;
}
// Convenience: send using a Login model body
Future<http.Response> loginWith(Login request) async {
final uri = Uri.parse(
'https://jupiter.nearle.app/live/api/v2/users/rider/login',
);
final body = request.toJson();
debugPrint('[AUTH][LOGIN] URL: ${uri.toString()}');
debugPrint('[AUTH][LOGIN] Body: ${json.encode(body)}');
final res = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
debugPrint('[AUTH][LOGIN] Status: ${res.statusCode}');
debugPrint('[AUTH][LOGIN] Response: ${res.body}');
return res;
}
// Convenience: parsed response as Login model
Future<Login> loginParsed({
required String contactNo,
required String deviceType,
required int configId,
required String deviceId,
required String fcmToken,
int? pin,
}) async {
final res = await login(
contactNo: contactNo,
deviceType: deviceType,
configId: configId,
deviceId: deviceId,
fcmToken: fcmToken,
pin: pin,
);
final Map<String, dynamic> jsonMap = res.body.isNotEmpty
? json.decode(res.body) as Map<String, dynamic>
: <String, dynamic>{};
debugPrint('[AUTH] Raw Login JSON: $jsonMap');
if (jsonMap.containsKey('details')) {
final details = jsonMap['details'];
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('userid', details['userid'] ?? 0);
await prefs.setInt('userId', details['userid'] ?? 0);
await prefs.setInt('shiftid', details['shiftid'] ?? 0);
await prefs.setInt('shiftId', details['shiftid'] ?? 0);
await prefs.setInt('logid', details['logid'] ?? 0);
await prefs.setInt('logId', details['logid'] ?? 0);
await prefs.setInt('riderid', details['riderid'] ?? 0);
await prefs.setInt('partnerid', details['partnerid'] ?? 0);
await prefs.setInt('partnerId', details['partnerid'] ?? 0);
await prefs.setInt('configid', details['configid'] ?? 0);
await prefs.setInt('logseconds', details['logseconds'] ?? 0);
await prefs.setInt('locationid', details['locationid'] ?? 0);
await prefs.setInt('tenantid', details['tenantid'] ?? 0);
await prefs.setInt('applocationid', details['applocationid'] ?? 0);
final String fcm = (details['userfcmtoken'] ?? '').toString();
if (fcm.isNotEmpty) {
await prefs.setString('userfcmtoken', fcm);
}
// Persist rider name variants for downstream usage (e.g. rider logs)
final String firstName = (details['firstname'] ?? '').toString();
final String lastName = (details['lastname'] ?? '').toString();
final String apiUsername = (details['username'] ?? '').toString();
final String combinedName = ('$firstName $lastName').trim();
if (apiUsername.isNotEmpty) {
await prefs.setString('username', apiUsername);
} else if (combinedName.isNotEmpty) {
await prefs.setString('username', combinedName);
}
if (firstName.isNotEmpty) {
await prefs.setString('firstname', firstName);
}
if (lastName.isNotEmpty) {
await prefs.setString('lastname', lastName);
}
if (details['onduty'] != null) {
final int od = (details['onduty'] is num)
? (details['onduty'] as num).toInt()
: int.tryParse('${details['onduty']}') ?? 0;
await prefs.setInt('onduty', od);
}
// Persist rider payout config (per-kilometer fuel/rider charge) if provided
if (details.containsKey('fuelcharge')) {
final double fuelCharge =
double.tryParse('${details['fuelcharge']}') ?? 0.0;
await prefs.setDouble('fuelcharge', fuelCharge);
}
// Backward compatibility with older field names
if (details.containsKey('firstmilecharge')) {
final double firstMileCharge =
double.tryParse('${details['firstmilecharge']}') ?? 0.0;
await prefs.setDouble('firstmilecharge', firstMileCharge);
} else if (details.containsKey('firstmilecharges')) {
final double firstMileCharge =
double.tryParse('${details['firstmilecharges']}') ?? 0.0;
await prefs.setDouble('firstmilecharge', firstMileCharge);
}
// Save shift window for header display
if (details['starttime'] != null) {
await prefs.setString('starttime', details['starttime'].toString());
}
await prefs.setString('endtime', details['endtime'].toString());
// Save delivery radius for geofencing (default 100m if not provided)
if (details['deliveryradius'] != null) {
final int radius = (details['deliveryradius'] is num)
? (details['deliveryradius'] as num).toInt()
: int.tryParse('${details['deliveryradius']}') ?? 100;
await prefs.setInt('deliveryradius', radius);
debugPrint('[AUTH] Saved deliveryradius: $radius meters');
} else {
await prefs.setInt('deliveryradius', 100); // Default
debugPrint('[AUTH] Saved default deliveryradius: 100 meters');
}
debugPrint(
'[AUTH] SharedPrefs Saved: '
'userid=${details['userid']}, shiftid=${details['shiftid']}, '
'logid=${details['logid']}, riderid=${details['riderid']},'
'partnerid=${details['partnerid']}, configid=${details['configid']}',
);
// Rider log creation is deferred until the rider goes ON duty.
//
// NOTE: We intentionally do NOT auto-navigate from here anymore.
// Navigation after login / PIN verification is handled in the UI flows
// (e.g. MPIN screen) so that riders cannot reach the homepage before
// successfully entering a valid PIN.
}
return Login.fromJson(jsonMap);
}
Future<http.Response> updatePin({
required int userId,
required int pin,
}) async {
final uri = Uri.parse(
'https://jupiter.nearle.app/live/api/v2/users/update',
);
final body = {'userid': userId, 'pin': pin};
debugPrint('[AUTH][UPDATE PIN] URL: ${uri.toString()}');
debugPrint('[AUTH][UPDATE PIN] Body: ${json.encode(body)}');
final res = await http.put(
uri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
debugPrint('[AUTH][UPDATE PIN] Status: ${res.statusCode}');
debugPrint('[AUTH][UPDATE PIN] Response: ${res.body}');
return res;
}
}

View File

@@ -0,0 +1,141 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:nearle/views/helpers/constants/apiconstants.dart';
class DeliveryProvider {
final http.Client _client;
DeliveryProvider({http.Client? client}) : _client = client ?? http.Client();
Future<http.Response> _getWithRetry(Uri uri, {int maxAttempts = 3}) async {
int attempt = 0;
while (true) {
attempt++;
try {
return await _client.get(uri);
} on SocketException catch (_) {
if (attempt >= maxAttempts) rethrow;
} on http.ClientException catch (_) {
if (attempt >= maxAttempts) rethrow;
}
// Exponential backoff: 300ms, 600ms
final delayMs = 300 * attempt;
await Future.delayed(Duration(milliseconds: delayMs));
}
}
Future<List<dynamic>> getDeliveryQueues({
required bool live,
required int userid,
String? orderstatus,
}) async {
final base = live ? ApiConstants.deliveryQueueLive : ApiConstants.deliveryQueueDev;
// Get current date in YYYY-MM-DD format
final now = DateTime.now();
final mm = now.month.toString().padLeft(2, '0');
final dd = now.day.toString().padLeft(2, '0');
final yyyy = now.year.toString();
final today = "$yyyy-$mm-$dd";
final qp = <String, String>{
'userid': userid.toString(),
'fromdate': today,
'todate': today,
't': DateTime.now().millisecondsSinceEpoch.toString(),
};
if (orderstatus != null && orderstatus.isNotEmpty) {
qp['orderstatus'] = orderstatus;
}
final uri = Uri.parse(base).replace(queryParameters: qp);
final res = await _getWithRetry(uri);
if (res.statusCode >= 200 && res.statusCode < 300) {
// Log URL and raw status
// ignore: avoid_print
print('[DELIVERIES][GET] URL: ${uri.toString()}');
final decoded = json.decode(res.body);
final data = decoded is Map<String, dynamic>
? (decoded['details'] ?? decoded['data'] ?? decoded)
: decoded;
if (data is List) {
try {
// Pretty log the list if deliveries are found
if (data.isNotEmpty) {
// ignore: avoid_print
// print('[DELIVERIES][GET] Data: ${json.encode(data)}'); // Heavy log, disabled for performance
} else {
// ignore: avoid_print
// print('[DELIVERIES][GET] Data: []');
}
} catch (_) {}
return data;
}
if (data is Map && data['items'] is List) return data['items'] as List;
return [];
}
throw Exception('Failed (${res.statusCode})');
}
Future<List<dynamic>> getDeliveryQueuesPicked({
required bool live,
required int userid,
}) async {
// Use v3 getdeliveries with fromdate/todate as today's date (dynamic)
final base = live
? ApiConstants.currentDeliveryV3Live
: ApiConstants.currentDeliveryV3Dev;
// Get current date in YYYY-MM-DD format (dynamically updated each day)
final now = DateTime.now();
final mm = now.month.toString().padLeft(2, '0');
final dd = now.day.toString().padLeft(2, '0');
final yyyy = now.year.toString();
final today = "$yyyy-$mm-$dd";
final qp = <String, String>{
'userid': userid.toString(),
'fromdate': today,
'todate': today,
't': DateTime.now().millisecondsSinceEpoch.toString(),
};
final uri = Uri.parse(base).replace(queryParameters: qp);
final res = await _getWithRetry(uri);
if (res.statusCode >= 200 && res.statusCode < 300) {
// ignore: avoid_print
print('[DELIVERIES][GET_PICKED] URL: ${uri.toString()}');
final decoded = json.decode(res.body);
// The API returns {"code":200,"details":[...],"message":"Success","status":true}
final data = decoded is Map<String, dynamic>
? (decoded['details'] ?? decoded['data'] ?? decoded)
: decoded;
if (data is List) {
try {
if (data.isNotEmpty) {
// ignore: avoid_print
// print('[DELIVERIES][GET_PICKED] Data: ${json.encode(data)}'); // Heavy log, disabled
} else {
// ignore: avoid_print
// print('[DELIVERIES][GET_PICKED] Data: []');
}
} catch (_) {}
return data;
}
if (data is Map && data['items'] is List) return data['items'] as List;
return [];
}
throw Exception('Failed (${res.statusCode})');
}
}

View File

@@ -0,0 +1,268 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart';
import 'package:http/io_client.dart';
import 'package:flutter/foundation.dart';
// Combined Deliverylog providers:
/// Hardcoded known-good IPs for hosts where carrier DNS returns broken CDN nodes.
/// Confirmed: 66.116.225.226 = 200 OK, 125.21.240.67 = 404/405.
const _knownGoodIPs = <String, String>{
'queue.workolik.com': '66.116.225.226',
};
/// Creates an IOClient that:
/// 1. Bypasses SSL certificate errors
/// 2. Forces known-good IPs to avoid broken CDN nodes from carrier DNS
/// 3. Manually does TLS upgrade with correct SNI (hostname, not IP)
IOClient _buildSslBypassClient() {
final httpClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
httpClient.connectionFactory =
(Uri uri, String? proxyHost, int? proxyPort) async {
final host = uri.host;
final port = uri.port;
InternetAddress? target;
final knownIP = _knownGoodIPs[host];
if (knownIP != null) {
target = InternetAddress(knownIP);
} else {
try {
final addresses = await InternetAddress.lookup(
host,
type: InternetAddressType.IPv4,
);
if (addresses.isNotEmpty) target = addresses.first;
} catch (_) {}
}
if (uri.scheme == 'https') {
final socketFuture =
Socket.connect(target ?? InternetAddress(host), port)
.then((plain) => SecureSocket.secure(
plain,
host: host,
onBadCertificate: (_) => true,
supportedProtocols: ['http/1.1'],
))
.then((s) => s as Socket);
return ConnectionTask.fromSocket<Socket>(socketFuture, () {});
}
return Socket.startConnect(target ?? InternetAddress(host), port);
};
return IOClient(httpClient);
}
class CreateDeliveryLogProvider {
Future<Map<String, dynamic>?> createDeliveryLog(
String urldata,
Map<String, dynamic> data, {
bool wrapInArray = true,
}) async {
Map<String, dynamic>? result;
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final body = json.encode(wrapInArray ? [data] : data);
final response = await client.post(
url,
body: body,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
).timeout(const Duration(seconds: 10));
debugPrint('createDeliveryLog url $urldata');
debugPrint(body);
debugPrint('createDeliveryLog response ${response.body}');
if (response.statusCode >= 200 && response.statusCode < 300) {
result = json.decode(response.body.toString()) as Map<String, dynamic>;
debugPrint('createDeliveryLog parsed ${result.toString()}');
} else {
debugPrint('createDeliveryLog failed: HTTP ${response.statusCode}');
}
} on TimeoutException catch (e) {
debugPrint('createDeliveryLog timeout: $e');
} catch (e) {
debugPrint('createDeliveryLog error: $e');
} finally {
client.close();
}
return result;
}
}
class UpdateDeliveryProvider {
Future<Map<String, dynamic>?> updateDelivery(
Map<String, dynamic> data,
String urldata,
) async {
Map<String, dynamic>? updateDeliveryResponse;
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final response = await client.put(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
).timeout(const Duration(seconds: 10));
debugPrint('updateDelivery url $urldata');
debugPrint('updateDelivery status ${response.statusCode}');
debugPrint('updateDelivery response ${response.body}');
debugPrint(json.encode(data));
if (response.statusCode >= 200 && response.statusCode < 300) {
updateDeliveryResponse =
json.decode(response.body) as Map<String, dynamic>;
} else {
debugPrint('updateDelivery failed: HTTP ${response.statusCode}');
}
} on TimeoutException catch (e) {
debugPrint('updateDelivery timeout: $e');
} catch (e) {
debugPrint('updateDelivery error: $e');
} finally {
client.close();
}
return updateDeliveryResponse;
}
Future<Map<String, dynamic>?> updateArrivedDelivery(
Map<String, dynamic> data,
String urldata,
) async {
Map<String, dynamic>? updateDeliveryResponse;
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final response = await client.put(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
).timeout(const Duration(seconds: 10));
debugPrint('updateArrived url $urldata');
debugPrint('updateArrived status ${response.statusCode}');
debugPrint(json.encode(data));
if (response.statusCode >= 200 && response.statusCode < 300) {
updateDeliveryResponse =
json.decode(response.body) as Map<String, dynamic>;
} else {
debugPrint('updateArrived failed: HTTP ${response.statusCode}');
}
} on TimeoutException catch (e) {
debugPrint('updateArrived timeout: $e');
} catch (e) {
debugPrint('updateArrived error: $e');
} finally {
client.close();
}
return updateDeliveryResponse;
}
Future<Map<String, dynamic>?> updatePickedDelivery(
Map<String, dynamic> data,
String urldata,
) async {
Map<String, dynamic>? updateDeliveryResponse;
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final response = await client.put(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
).timeout(const Duration(seconds: 10));
debugPrint('updatePicked url $urldata');
debugPrint('updatePicked status ${response.statusCode}');
debugPrint(json.encode(data));
if (response.statusCode >= 200 && response.statusCode < 300) {
updateDeliveryResponse =
json.decode(response.body) as Map<String, dynamic>;
} else {
debugPrint('updatePicked failed: HTTP ${response.statusCode}');
}
} on TimeoutException catch (e) {
debugPrint('updatePicked timeout: $e');
} catch (e) {
debugPrint('updatePicked error: $e');
} finally {
client.close();
}
return updateDeliveryResponse;
}
Future<Map<String, dynamic>?> updateActiveDelivery(
Map<String, dynamic> data,
String urldata,
) async {
Map<String, dynamic>? updateDeliveryResponse;
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final response = await client.put(
url,
body: json.encode(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
).timeout(const Duration(seconds: 10));
debugPrint('updateActive url $urldata');
debugPrint('updateActive status ${response.statusCode}');
debugPrint(json.encode(data));
if (response.statusCode >= 200 && response.statusCode < 300) {
updateDeliveryResponse =
json.decode(response.body) as Map<String, dynamic>;
} else {
debugPrint('updateActive failed: HTTP ${response.statusCode}');
}
} on TimeoutException catch (e) {
debugPrint('updateActive timeout: $e');
} catch (e) {
debugPrint('updateActive error: $e');
} finally {
client.close();
}
return updateDeliveryResponse;
}
}
class GetDeliveryLogProvider {
Future<Map<String, dynamic>?> getDeliveryLog(String urldata) async {
Map<String, dynamic>? result;
final client = _buildSslBypassClient();
try {
final url = Uri.parse(urldata);
final response = await client.get(url, headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
});
debugPrint('getDeliveryLog url $urldata');
debugPrint('getDeliveryLog response ${response.body}');
if (response.statusCode >= 200 && response.statusCode < 300) {
result =
json.decode(response.body.toString()) as Map<String, dynamic>;
}
} catch (e) {
debugPrint('getDeliveryLog error: $e');
} finally {
client.close();
}
return result;
}
}

View File

@@ -0,0 +1,402 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:nearle/helpers/http_overrides.dart';
// Top-level background handler required by Firebase Messaging
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
HttpOverrides.global = MyHttpOverrides();
try {
await Firebase.initializeApp();
} catch (_) {}
await NotificationServce.display(message);
}
class NotificationServce {
static final FirebaseMessaging _firebaseMessaging =
FirebaseMessaging.instance;
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
static final AudioPlayer _player = AudioPlayer();
static String _channelId = 'Nearle';
static bool _isPlaying = false;
static const AndroidNotificationChannel channel = AndroidNotificationChannel(
'Nearle',
'Nearle Notification',
description: 'Channel for Nearle notifications',
importance: Importance.max,
playSound: true,
enableVibration: true,
showBadge: true,
);
static Future<void> initialize(BuildContext context) async {
try {
final prefs = await SharedPreferences.getInstance();
final alreadyInit = prefs.getBool('notifications_init_done') ?? false;
if (alreadyInit) {
return;
}
await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
final existing = (prefs.getString('order_alert_sound') ?? '').trim();
if (existing.isEmpty) {
await prefs.setString('order_alert_sound', 'assets/audio/alert-1.mp3');
}
await prefs.setBool('notifications_init_done', true);
} catch (_) {}
await _notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await _applyChannelSoundFromPrefs();
const InitializationSettings initializationSettings =
InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(
requestSoundPermission: true,
requestBadgePermission: true,
requestAlertPermission: true,
defaultPresentSound: true,
defaultPresentBadge: true,
defaultPresentBanner: true,
defaultPresentAlert: true,
defaultPresentList: true,
),
);
await _notificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) async {},
);
RemoteMessage? initialMessage =
await _firebaseMessaging.getInitialMessage();
if (initialMessage != null) {
await _handleInitialMessage(initialMessage);
}
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
await _handleMessage(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
await _handleMessageOpenedApp(message);
});
}
// ✅ Removed duplicated background handler (this was breaking your notifications)
static Future<void> _handleInitialMessage(RemoteMessage message) async {
if (message.notification != null) {
await display(message);
}
}
static Future<void> _handleMessage(RemoteMessage message) async {
if (message.notification != null) {
await display(message);
}
}
static Future<void> _handleMessageOpenedApp(RemoteMessage message) async {}
static Future<void> _applyChannelSoundFromPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
String sel = (prefs.getString('order_alert_sound') ?? '').trim();
if (sel.isEmpty) {
sel = 'assets/audio/alert-1.mp3';
}
final fileName = sel.split('/').last;
final base = fileName.split('.').first;
final rawName = base.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
final androidImpl = _notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
if (androidImpl != null) {
_channelId = 'Nearle_$rawName';
final custom = AndroidNotificationChannel(
_channelId,
'Nearle Notification',
description: 'Channel for Nearle notifications',
importance: Importance.max,
playSound: true,
sound: RawResourceAndroidNotificationSound(rawName),
enableVibration: true,
showBadge: true,
);
await androidImpl.createNotificationChannel(custom);
}
} catch (_) {}
}
static Future<String?> _downloadAndSaveImage(
String imageUrl, String fileName) async {
try {
final directory = await getTemporaryDirectory();
final filePath = '${directory.path}/$fileName';
final response = await http.get(Uri.parse(imageUrl));
if (response.statusCode == 200) {
final file = File(filePath);
await file.writeAsBytes(response.bodyBytes);
return filePath;
}
return null;
} catch (_) {
return null;
}
}
static String? _extractImageUrl(RemoteMessage message) {
String? imageUrl = message.data['image'] as String?;
imageUrl ??= message.notification?.android?.imageUrl;
imageUrl ??= message.notification?.apple?.imageUrl;
return imageUrl;
}
static Future<void> _playSelectedSound({int times = 1}) async {
try {
if (_isPlaying) return;
_isPlaying = true;
final prefs = await SharedPreferences.getInstance();
String selected = (prefs.getString('order_alert_sound') ?? '').trim();
if (selected.isEmpty) {
selected = 'assets/audio/alert-1.mp3';
}
final rel = selected.startsWith('assets/')
? selected.replaceFirst('assets/', '')
: selected;
await _player.stop();
await _player.setReleaseMode(ReleaseMode.stop);
for (int i = 0; i < times; i++) {
await _player.play(AssetSource(rel));
try {
await _player.onPlayerComplete.first;
} catch (_) {}
if (i < times - 1) {
await Future.delayed(const Duration(milliseconds: 120));
}
}
} catch (_) {} finally {
_isPlaying = false;
}
}
/// Lightweight local notification helper for in-app events (no FCM message).
static Future<void> showLocalNotification({
required String title,
required String body,
bool playSound = true,
String? payload,
}) async {
try {
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
_channelId,
'Nearle Notification',
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
playSound: playSound,
enableVibration: true,
channelShowBadge: true,
ongoing: false,
autoCancel: true,
),
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: playSound,
presentList: true,
presentBanner: true,
),
);
await _notificationsPlugin.show(
id,
title,
body,
notificationDetails,
payload: payload,
);
} catch (_) {}
}
static Future<void> display(RemoteMessage message) async {
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final payload = jsonEncode({'id': id.toString(), 'data': message.data});
final appState = WidgetsBinding.instance.lifecycleState;
final bool isForeground = appState == AppLifecycleState.resumed;
if (isForeground) {
await _playSelectedSound(times: 5);
}
NotificationDetails notificationDetails;
final imageUrl = _extractImageUrl(message);
String? persistedImageUrl = imageUrl;
String? persistedImagePath;
if (imageUrl != null && imageUrl.isNotEmpty) {
final imagePath = await _downloadAndSaveImage(
imageUrl,
'notification_image.jpg',
);
persistedImagePath = imagePath;
notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
_channelId,
'Nearle Notification',
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
playSound: !isForeground,
enableVibration: true,
fullScreenIntent: true,
channelShowBadge: true,
ongoing: false,
autoCancel: true,
styleInformation: imagePath != null
? BigPictureStyleInformation(FilePathAndroidBitmap(imagePath))
: const DefaultStyleInformation(true, true),
),
iOS: const DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
presentList: true,
presentBanner: true,
),
);
} else {
notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
_channelId,
'Nearle Notification',
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
playSound: !isForeground,
enableVibration: true,
fullScreenIntent: true,
channelShowBadge: true,
ongoing: false,
autoCancel: true,
),
iOS: const DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
presentList: true,
presentBanner: true,
),
);
}
if (isForeground) {
final ctx = Get.context;
if (ctx != null) {
final title =
message.notification?.title ?? message.data['title'] ?? 'Nearle';
final body = message.notification?.body ?? message.data['body'] ?? '';
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.w700)),
if (body.isNotEmpty) Text(body),
],
),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
duration: const Duration(seconds: 3),
),
);
}
} else {
await _notificationsPlugin.show(
id,
message.notification?.title ?? message.data['title'] ?? 'Nearle',
message.notification?.body ?? message.data['body'] ?? 'Notification',
notificationDetails,
payload: payload,
);
}
try {
final prefs = await SharedPreferences.getInstance();
final nowIso = DateTime.now().toIso8601String();
final title =
message.notification?.title ?? message.data['title'] ?? 'Nearle';
final body =
message.notification?.body ?? message.data['body'] ?? '';
final entry = {
'id': id,
'title': title,
'body': body,
'time': nowIso,
'data': message.data,
if (persistedImageUrl != null) 'imageUrl': persistedImageUrl,
if (persistedImagePath != null) 'imagePath': persistedImagePath,
};
final existingRaw = prefs.getString('notifications_log');
List<dynamic> list = [];
if (existingRaw != null && existingRaw.isNotEmpty) {
try {
list = jsonDecode(existingRaw) as List<dynamic>;
} catch (_) {}
}
list.insert(0, entry);
if (list.length > 100) list = list.sublist(0, 100);
await prefs.setString('notifications_log', jsonEncode(list));
} catch (_) {}
}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:nearle/Models/summary/riderweeklykms.dart';
import 'package:nearle/controllers/riderkm.dart';
class RiderWeeklyKmProvider extends ChangeNotifier {
final RiderWeeklyKmController _controller = RiderWeeklyKmController();
bool isLoading = false;
String? error;
List<RiderWeeklyKms> kmsList = [];
double totalKms = 0.0;
Future<void> fetchRiderWeeklyKms(int userId) async {
isLoading = true;
error = null;
notifyListeners();
try {
final result = await _controller.getRiderWeeklyKms(userId);
kmsList = result['details'];
totalKms = result['total_kms'];
} catch (e) {
error = e.toString();
}
isLoading = false;
notifyListeners();
}
}

View File

@@ -0,0 +1,33 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:nearle/models/summary/deliverystats.dart';
import 'package:nearle/views/helpers/constants/apiconstants.dart';
class SummaryProvider {
final String baseUrl = ApiConstants.summaryApiLive;
Future<DeliveryStats?> fetchSummaryStats(int userId) async {
final url = Uri.parse('$baseUrl/getdeliverystats?userid=$userId');
try {
print(url);
final response = await http.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
if (decoded['status'] == true && decoded['data'] != null) {
return DeliveryStats.fromJson(decoded['data']);
} else {
print('API returned false status: ${decoded['message']}');
}
} else {
print('something went wrong');
}
} catch (e) {
print('something went wrong');
}
return null;
}
}

View File

@@ -0,0 +1,127 @@
import 'dart:convert';
import 'dart:io';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:nearle/Models/supportticket/support_ticket.dart';
class SupportTicketController extends GetxController {
final RxList<SupportTicketModel> tickets = <SupportTicketModel>[].obs;
final RxBool isLoading = true.obs;
final RxString errorMessage = ''.obs;
final RxBool isSubmitting = false.obs;
@override
void onInit() {
super.onInit();
fetchTickets();
}
Future<void> fetchTickets() async {
try {
isLoading(true);
errorMessage('');
const userId = 1242;
final url = Uri.parse(
'https://jupiter.nearle.app/live/api/v1/partners/getridersupport/?userid=$userId');
final response = await http.get(url, headers: {
'Accept': 'application/json',
});
if (response.statusCode != 200) {
throw Exception('Server error: ${response.statusCode}');
}
final Map<String, dynamic> jsonResponse = json.decode(response.body);
if (jsonResponse['status'] != true) {
throw Exception(jsonResponse['message'] ?? 'Unknown error');
}
final List<dynamic> data = jsonResponse['data'];
tickets.assignAll(data.map((e) => SupportTicketModel.fromJson(e)).toList());
} catch (e) {
errorMessage(e.toString());
} finally {
isLoading(false);
}
}
Future<bool> createTicket({
required int userid,
required String category,
required String priority,
required String subject,
required String issue,
List<XFile>? attachments,
}) async {
try {
isSubmitting(true);
// Step 1: Upload image if attached (adjust endpoint if needed)
String? imageUrl;
if (attachments != null && attachments.isNotEmpty) {
// For simplicity, assume first image; upload to a temp endpoint or your main one
final imageFile = File(attachments.first.path);
final imageBytes = await imageFile.readAsBytes();
final imageName = attachments.first.name;
// Example image upload (replace with your actual image upload endpoint)
final uploadUrl = Uri.parse('https://jupiter.nearle.app/live/api/v1/partners/uploadimage/'); // Adjust URL
final imageRequest = http.MultipartRequest('POST', uploadUrl)
..files.add(http.MultipartFile.fromBytes('image', imageBytes, filename: imageName));
imageRequest.headers['Accept'] = 'application/json';
final imageResponse = await imageRequest.send();
if (imageResponse.statusCode == 200) {
final imageJson = await http.Response.fromStream(imageResponse);
imageUrl = json.decode(imageJson.body)['image_url']; // Assume response has 'image_url'
} else {
throw Exception('Image upload failed');
}
}
// Step 2: Create ticket with POST
final postUrl = Uri.parse('https://jupiter.nearle.app/live/api/v1/partners/createridersupport/');
final body = json.encode({
'userid': userid,
'category': category,
'priority': priority,
'subject': subject,
'issue': issue,
'image': imageUrl, // null if no image
});
final response = await http.post(
postUrl,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: body,
);
if (response.statusCode != 200) {
throw Exception('Failed to create ticket: ${response.statusCode}');
}
final Map<String, dynamic> jsonResponse = json.decode(response.body);
if (jsonResponse['status'] != true) {
throw Exception(jsonResponse['message'] ?? 'Unknown error');
}
// Refresh tickets to show new one
await fetchTickets();
return true;
} catch (e) {
errorMessage(e.toString());
return false;
} finally {
isSubmitting(false);
}
}
}