initial commit: push everything
This commit is contained in:
244
lib/providers/Riderlog/riderlog_provider.dart
Normal file
244
lib/providers/Riderlog/riderlog_provider.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
200
lib/providers/auth/auth_provider.dart
Normal file
200
lib/providers/auth/auth_provider.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
141
lib/providers/delivery/delivery_provider.dart
Normal file
141
lib/providers/delivery/delivery_provider.dart
Normal 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})');
|
||||
}
|
||||
}
|
||||
268
lib/providers/deliverylog/deliverylog_provider.dart
Normal file
268
lib/providers/deliverylog/deliverylog_provider.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
402
lib/providers/notifications/notificationservce.dart
Normal file
402
lib/providers/notifications/notificationservce.dart
Normal 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 (_) {}
|
||||
}
|
||||
}
|
||||
32
lib/providers/summary/riderweeklykms.dart
Normal file
32
lib/providers/summary/riderweeklykms.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
lib/providers/summary/summary.dart
Normal file
33
lib/providers/summary/summary.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
127
lib/providers/support/support_ticket.dart
Normal file
127
lib/providers/support/support_ticket.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user