package com.clickdelivery.nativebridge import android.Manifest import android.app.Activity import android.content.* import android.content.pm.PackageManager import android.location.* import android.net.Uri import android.provider.MediaStore import android.util.Base64 import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule import com.google.firebase.messaging.FirebaseMessaging import java.util.UUID class ClickDeliveryNativeModule(private val ctx:ReactApplicationContext):ReactContextBaseJavaModule(ctx),ActivityEventListener,LocationListener{ private var locationManager:LocationManager?=null;private var photoPromise:Promise?=null;private var photoUri:Uri?=null init{ctx.addActivityEventListener(this)} override fun getName()="ClickDeliveryNative" @ReactMethod fun secureSet(k:String,v:String,p:Promise){try{SecureStore.set(ctx,k,v);p.resolve(true)}catch(e:Exception){p.reject("SECURE_SET",e)}} @ReactMethod fun secureGet(k:String,p:Promise){try{p.resolve(SecureStore.get(ctx,k))}catch(e:Exception){p.reject("SECURE_GET",e)}} @ReactMethod fun secureDelete(k:String,p:Promise){SecureStore.delete(ctx,k);p.resolve(true)} @ReactMethod fun getInstallationId(p:Promise){val prefs=ctx.getSharedPreferences("clickdelivery_install",0);var id=prefs.getString("id",null);if(id==null){id=UUID.randomUUID().toString();prefs.edit().putString("id",id).apply()};p.resolve(id)} @ReactMethod fun getApiBaseUrl(p:Promise){try{val ai=ctx.packageManager.getApplicationInfo(ctx.packageName,PackageManager.GET_META_DATA);p.resolve(ai.metaData.getString("CLICKDELIVERY_API_BASE_URL"))}catch(e:Exception){p.reject("BASE_URL",e)}} @ReactMethod fun getDeviceName(p:Promise){p.resolve("${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}")} @ReactMethod fun getAppVersion(p:Promise){val i=ctx.packageManager.getPackageInfo(ctx.packageName,0);p.resolve(i.versionName)} @ReactMethod fun getFcmToken(p:Promise){FirebaseMessaging.getInstance().token.addOnSuccessListener{p.resolve(it)}.addOnFailureListener{p.reject("FCM_TOKEN",it)}} private fun map(loc:Location):WritableMap=Arguments.createMap().apply{putDouble("latitude",loc.latitude);putDouble("longitude",loc.longitude);putDouble("accuracy",loc.accuracy.toDouble());putDouble("speed",loc.speed.toDouble());putDouble("heading",loc.bearing.toDouble());putDouble("timestamp",loc.time.toDouble())} @ReactMethod fun getCurrentLocation(p:Promise){if(ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)!=PackageManager.PERMISSION_GRANTED){p.reject("LOCATION_PERMISSION","ACCESS_FINE_LOCATION required");return};val lm=ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager;val loc=listOf(LocationManager.GPS_PROVIDER,LocationManager.NETWORK_PROVIDER).mapNotNull{runCatching{lm.getLastKnownLocation(it)}.getOrNull()}.maxByOrNull{it.time};if(loc!=null)p.resolve(map(loc)) else p.reject("LOCATION_UNAVAILABLE","No last location available")} @ReactMethod fun startLocationTracking(intervalMs:Double,p:Promise){if(ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)!=PackageManager.PERMISSION_GRANTED){p.reject("LOCATION_PERMISSION","ACCESS_FINE_LOCATION required");return};locationManager=ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager;runCatching{locationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER,intervalMs.toLong().coerceAtLeast(3000),5f,this)};runCatching{locationManager?.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,intervalMs.toLong().coerceAtLeast(3000),5f,this)};p.resolve(true)} @ReactMethod fun stopLocationTracking(p:Promise){runCatching{locationManager?.removeUpdates(this)};p.resolve(true)} @ReactMethod fun startBackgroundTracking(intervalMs:Double,p:Promise){val i=Intent(ctx,DriverLocationService::class.java).putExtra("interval_ms",intervalMs.toLong());try{if(android.os.Build.VERSION.SDK_INT>=26)ctx.startForegroundService(i) else ctx.startService(i);p.resolve(true)}catch(e:Exception){p.reject("BACKGROUND_TRACKING",e)}} @ReactMethod fun stopBackgroundTracking(p:Promise){ctx.stopService(Intent(ctx,DriverLocationService::class.java));p.resolve(true)} override fun onLocationChanged(location:Location){ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("ClickDeliveryLocation",map(location))} @ReactMethod fun capturePhoto(p:Promise){val a=currentActivity?:run{p.reject("NO_ACTIVITY","Activity unavailable");return};val values=android.content.ContentValues().apply{put(MediaStore.Images.Media.DISPLAY_NAME,"clickdelivery_${System.currentTimeMillis()}.jpg");put(MediaStore.Images.Media.MIME_TYPE,"image/jpeg")};val uri=ctx.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)?:run{p.reject("CAMERA_URI","Cannot create media URI");return};photoPromise=p;photoUri=uri;val intent=Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT,uri);try{a.startActivityForResult(intent,7017)}catch(e:Exception){photoPromise=null;photoUri=null;p.reject("CAMERA_START",e)}} override fun onActivityResult(activity:Activity?,requestCode:Int,resultCode:Int,data:Intent?){if(requestCode!=7017)return;val p=photoPromise;val u=photoUri;photoPromise=null;photoUri=null;if(p==null)return;if(resultCode==Activity.RESULT_OK&&u!=null){val m=Arguments.createMap();m.putString("uri",u.toString());m.putString("mime_type","image/jpeg");p.resolve(m)}else p.reject("CAMERA_CANCELLED","Camera cancelled")} override fun onNewIntent(intent:Intent?){} @ReactMethod fun readContentUriBase64(uri:String,p:Promise){try{ctx.contentResolver.openInputStream(Uri.parse(uri)).use{input->if(input==null)throw IllegalStateException("Cannot open URI");p.resolve(Base64.encodeToString(input.readBytes(),Base64.NO_WRAP))}}catch(e:Exception){p.reject("READ_URI",e)}} @ReactMethod fun uploadContentUri(baseUrl:String,path:String,accessToken:String,uploadToken:String,uri:String,mime:String,p:Promise){Thread{try{val c=(java.net.URL(baseUrl.trimEnd('/')+path).openConnection() as java.net.HttpURLConnection);c.requestMethod="PUT";c.connectTimeout=15000;c.readTimeout=30000;c.doOutput=true;c.setRequestProperty("Content-Type",mime);c.setRequestProperty("Authorization","Bearer $accessToken");c.setRequestProperty("x-upload-token",uploadToken);ctx.contentResolver.openInputStream(Uri.parse(uri)).use{input->if(input==null)throw IllegalStateException("Cannot open URI");c.outputStream.use{out->input.copyTo(out)}};val code=c.responseCode;val stream=if(code<400)c.inputStream else c.errorStream;val text=stream?.bufferedReader()?.use{it.readText()}.orEmpty();c.disconnect();if(code>=400)p.reject("UPLOAD_HTTP_$code",text) else p.resolve(text)}catch(e:Exception){p.reject("UPLOAD_CONTENT_URI",e)}}.start()} }