Update: 2026-05-15 03:09:36
This commit is contained in:
@@ -21,6 +21,10 @@ import androidx.core.content.ContextCompat
|
||||
import com.jordanbot.autoride.service.BotForegroundService
|
||||
import com.jordanbot.autoride.service.OverlayService
|
||||
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import com.jordanbot.autoride.subscription.SubscriptionManager
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var tvStatus: TextView
|
||||
@@ -31,10 +35,22 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Load cached subscription data
|
||||
SubscriptionManager.loadLocalCache(this)
|
||||
|
||||
// Check subscription with server
|
||||
lifecycleScope.launch {
|
||||
SubscriptionManager.checkSubscription(this@MainActivity)
|
||||
}
|
||||
|
||||
tvStatus = findViewById(R.id.tv_status)
|
||||
btnStart = findViewById(R.id.btn_start)
|
||||
btnStop = findViewById(R.id.btn_stop)
|
||||
|
||||
findViewById<Button>(R.id.btn_subscriptions).setOnClickListener {
|
||||
startActivity(Intent(this, SubscriptionActivity::class.java))
|
||||
}
|
||||
|
||||
findViewById<Button>(R.id.btn_notification).setOnClickListener {
|
||||
startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.jordanbot.autoride
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.jordanbot.autoride.api.ActivateSubscriptionRequest
|
||||
import com.jordanbot.autoride.api.ApiClient
|
||||
import com.jordanbot.autoride.subscription.SubscriptionManager
|
||||
import com.jordanbot.autoride.utils.DeviceUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class SubscriptionActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var tvStatus: TextView
|
||||
private lateinit var btnBasic: Button
|
||||
private lateinit var btnPro: Button
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_subscription)
|
||||
|
||||
tvStatus = findViewById(R.id.tv_current_status)
|
||||
btnBasic = findViewById(R.id.btn_subscribe_basic)
|
||||
btnPro = findViewById(R.id.btn_subscribe_pro)
|
||||
|
||||
updateStatusUI()
|
||||
|
||||
btnBasic.setOnClickListener { activatePlan("basic") }
|
||||
btnPro.setOnClickListener { activatePlan("pro") }
|
||||
}
|
||||
|
||||
private fun updateStatusUI() {
|
||||
val plan = SubscriptionManager.currentPlan
|
||||
val limit = SubscriptionManager.ridesLimit
|
||||
val today = SubscriptionManager.ridesToday
|
||||
|
||||
val planText = when(plan) {
|
||||
"basic" -> "أساسي ($limit طلب / يوم)"
|
||||
"pro" -> "احترافي (لا محدود)"
|
||||
"annual" -> "سنوي (لا محدود)"
|
||||
else -> "مجاني (1 طلب / يوم)"
|
||||
}
|
||||
|
||||
tvStatus.text = "الخطة الحالية: $planText\nاستهلاك اليوم: $today"
|
||||
}
|
||||
|
||||
private fun activatePlan(plan: String) {
|
||||
// In a real app, integrate payment gateway here.
|
||||
// For demonstration, we just call the API directly.
|
||||
val fingerprint = DeviceUtils.getDeviceFingerprint(this)
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
ApiClient.service.activateSubscription(
|
||||
ActivateSubscriptionRequest(fingerprint, plan, "DEMO_REF_123")
|
||||
)
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
Toast.makeText(this@SubscriptionActivity, "تم تفعيل الاشتراك بنجاح!", Toast.LENGTH_SHORT).show()
|
||||
// Re-check subscription to update local cache
|
||||
SubscriptionManager.checkSubscription(this@SubscriptionActivity)
|
||||
updateStatusUI()
|
||||
} else {
|
||||
Toast.makeText(this@SubscriptionActivity, "فشل تفعيل الاشتراك: ${response.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this@SubscriptionActivity, "حدث خطأ في الاتصال", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,41 @@ data class ApiResponse(
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class CheckSubscriptionRequest(val fingerprint: String)
|
||||
data class CheckSubscriptionResponse(
|
||||
val success: Boolean,
|
||||
val plan: String?,
|
||||
val expires_at: String?,
|
||||
val rides_today: Int?,
|
||||
val rides_limit: Int?,
|
||||
val can_accept: Boolean?
|
||||
)
|
||||
|
||||
data class ActivateSubscriptionRequest(
|
||||
val fingerprint: String,
|
||||
val plan: String,
|
||||
val payment_ref: String?
|
||||
)
|
||||
|
||||
data class ActivateSubscriptionResponse(
|
||||
val success: Boolean,
|
||||
val message: String?,
|
||||
val plan: String?,
|
||||
val expires_at: String?
|
||||
)
|
||||
|
||||
interface BackendApiService {
|
||||
@POST("api/rides.php")
|
||||
suspend fun logRide(@Body request: RideLogRequest): ApiResponse
|
||||
|
||||
@POST("api/location.php")
|
||||
suspend fun updateBulkLocation(@Body request: BulkLocationRequest): ApiResponse
|
||||
|
||||
@POST("api/subscription/check.php")
|
||||
suspend fun checkSubscription(@Body request: CheckSubscriptionRequest): CheckSubscriptionResponse
|
||||
|
||||
@POST("api/subscription/activate.php")
|
||||
suspend fun activateSubscription(@Body request: ActivateSubscriptionRequest): ActivateSubscriptionResponse
|
||||
}
|
||||
|
||||
object ApiClient {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.jordanbot.autoride.engine
|
||||
|
||||
import android.util.Log
|
||||
import com.jordanbot.autoride.model.RideRequest
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
object RideDataMerger {
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
private var lastRequest: RideRequest? = null
|
||||
private var lastTimestamp: Long = 0
|
||||
private const val MERGE_WINDOW_MS = 5000L
|
||||
|
||||
var onRideReady: ((RideRequest) -> Unit)? = null
|
||||
|
||||
@Synchronized
|
||||
fun updateFromNotification(request: RideRequest) {
|
||||
Log.d("JordanBot", "🔔 Merger received Notification data: ${request.priceJod} JOD")
|
||||
merge(request)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateFromScreen(request: RideRequest) {
|
||||
Log.d("JordanBot", "👁️ Merger received Screen data: ${request.priceJod} JOD")
|
||||
merge(request)
|
||||
}
|
||||
|
||||
private fun merge(newPart: RideRequest) {
|
||||
val now = System.currentTimeMillis()
|
||||
val current = lastRequest
|
||||
|
||||
if (current == null || (now - lastTimestamp) > MERGE_WINDOW_MS || current.appPackage != newPart.appPackage) {
|
||||
// New request sequence
|
||||
lastRequest = newPart
|
||||
lastTimestamp = now
|
||||
|
||||
// Wait a bit to see if more data comes from the other source before emitting
|
||||
scope.launch {
|
||||
delay(1500) // Wait 1.5s for the other source
|
||||
emitIfReady()
|
||||
}
|
||||
} else {
|
||||
// Merge into current
|
||||
lastRequest = current.copy(
|
||||
priceJod = newPart.priceJod ?: current.priceJod,
|
||||
minutesAway = newPart.minutesAway ?: current.minutesAway,
|
||||
distanceKm = newPart.distanceKm ?: current.distanceKm,
|
||||
pickupAddress = newPart.pickupAddress ?: current.pickupAddress,
|
||||
dropoffAddress = newPart.dropoffAddress ?: current.dropoffAddress,
|
||||
title = if (newPart.title.isNotEmpty() && newPart.title != "Screen Scrape") newPart.title else current.title,
|
||||
text = if (newPart.text.isNotEmpty() && newPart.text != current.text) "${current.text} | ${newPart.text}" else current.text
|
||||
)
|
||||
lastTimestamp = now
|
||||
emitIfReady()
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitIfReady() {
|
||||
val ride = lastRequest ?: return
|
||||
|
||||
// Criteria for "Ready": We have at least a price
|
||||
if (ride.priceJod != null) {
|
||||
Log.d("JordanBot", "🔀 MERGED RIDE READY: $ride")
|
||||
onRideReady?.invoke(ride)
|
||||
lastRequest = null // Clear to avoid double emission
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
@@ -24,13 +25,26 @@ class BotForegroundService : Service() {
|
||||
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private val locationBuffer = mutableListOf<LocationPoint>()
|
||||
|
||||
private val UPLOAD_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30)
|
||||
private val TRACK_INTERVAL_MS = TimeUnit.SECONDS.toMillis(3)
|
||||
|
||||
// Battery-optimized intervals
|
||||
private val UPLOAD_INTERVAL_MS = TimeUnit.MINUTES.toMillis(5) // Upload every 5 minutes
|
||||
private val TRACK_INTERVAL_MS = TimeUnit.SECONDS.toMillis(15) // GPS poll every 15 seconds
|
||||
private val MIN_DISPLACEMENT_METERS = 15f // Ignore movement < 15m
|
||||
|
||||
// Displacement filter: track last known location
|
||||
private var lastTrackedLocation: Location? = null
|
||||
|
||||
private val locationCallback = object : LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
locationResult.lastLocation?.let { location ->
|
||||
// Displacement filter: skip if driver hasn't moved 15 meters
|
||||
val last = lastTrackedLocation
|
||||
if (last != null && last.distanceTo(location) < MIN_DISPLACEMENT_METERS) {
|
||||
return // Driver is stationary, save battery
|
||||
}
|
||||
|
||||
lastTrackedLocation = location
|
||||
|
||||
val point = LocationPoint(
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
@@ -58,7 +72,7 @@ class BotForegroundService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("Jordan Bot يعمل حالياً")
|
||||
.setContentText("نحن نراقب الإشعارات ونحدث الخرائط.")
|
||||
.setContentText("نبحث عن طلبات قريبة منك...")
|
||||
.setSmallIcon(R.drawable.bg_bubble)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
@@ -69,7 +83,10 @@ class BotForegroundService : Service() {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun startTracking() {
|
||||
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, TRACK_INTERVAL_MS)
|
||||
val locationRequest = LocationRequest.Builder(
|
||||
Priority.PRIORITY_BALANCED_POWER_ACCURACY, TRACK_INTERVAL_MS
|
||||
)
|
||||
.setMinUpdateDistanceMeters(MIN_DISPLACEMENT_METERS)
|
||||
.setMinUpdateIntervalMillis(TRACK_INTERVAL_MS)
|
||||
.build()
|
||||
|
||||
|
||||
@@ -2,70 +2,143 @@ package com.jordanbot.autoride.service
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.accessibilityservice.AccessibilityServiceInfo
|
||||
import android.accessibilityservice.GestureDescription
|
||||
import android.graphics.Path
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import com.jordanbot.autoride.engine.RideDataMerger
|
||||
import com.jordanbot.autoride.filter.FilterEngine
|
||||
import com.jordanbot.autoride.model.RideRequest
|
||||
|
||||
class RideAccessibilityService : AccessibilityService() {
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val filterEngine = FilterEngine()
|
||||
|
||||
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
||||
if (event.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
|
||||
event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return
|
||||
val rootNode = rootInActiveWindow ?: return
|
||||
val packageName = event.packageName?.toString() ?: ""
|
||||
|
||||
val source = event.source ?: return
|
||||
val packageName = event.packageName?.toString() ?: return
|
||||
// Only process supported apps
|
||||
if (!isSupportedApp(packageName)) return
|
||||
|
||||
// Check if this package was recently flagged by NotificationListener to be accepted
|
||||
if (RideNotificationListener.pendingAcceptPackage == packageName) {
|
||||
val timeSinceRequest = System.currentTimeMillis() - RideNotificationListener.lastRequestTime
|
||||
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED ||
|
||||
event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
|
||||
|
||||
// Only accept if within 15 seconds of notification
|
||||
if (timeSinceRequest < 15000) {
|
||||
findAndClickAcceptButton(source)
|
||||
} else {
|
||||
RideNotificationListener.pendingAcceptPackage = null // Expired
|
||||
val screenData = scrapeScreen(rootNode, packageName)
|
||||
if (screenData != null) {
|
||||
RideDataMerger.updateFromScreen(screenData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAndClickAcceptButton(rootNode: AccessibilityNodeInfo) {
|
||||
// Arabic and English accept terms
|
||||
val acceptTerms = listOf("Accept", "قبول", "Tap to accept", "اضغط للقبول")
|
||||
private fun isSupportedApp(pkg: String): Boolean {
|
||||
return pkg == "com.ubercab.driver" || pkg == "com.careem.adma" || pkg == "me.jeeny.driver"
|
||||
}
|
||||
|
||||
private fun scrapeScreen(root: AccessibilityNodeInfo, pkg: String): RideRequest? {
|
||||
val allText = mutableListOf<String>()
|
||||
collectAllText(root, allText)
|
||||
val combinedText = allText.joinToString(" ")
|
||||
|
||||
// Extract price
|
||||
val priceRegex = """(\d+\.?\d*)\s*(JOD|د\.أ|دينار)""".toRegex()
|
||||
val priceMatch = priceRegex.find(combinedText)
|
||||
val price = priceMatch?.groupValues?.get(1)?.toDoubleOrNull()
|
||||
|
||||
if (price != null) {
|
||||
Log.d("JordanBot", "👁️ Scraped price from screen: $price $pkg")
|
||||
|
||||
return RideRequest(
|
||||
appPackage = pkg,
|
||||
priceJod = price,
|
||||
minutesAway = null,
|
||||
distanceKm = null,
|
||||
title = "Screen Scrape",
|
||||
text = combinedText
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun collectAllText(node: AccessibilityNodeInfo?, list: MutableList<String>) {
|
||||
if (node == null) return
|
||||
node.text?.let { list.add(it.toString()) }
|
||||
for (i in 0 until node.childCount) {
|
||||
collectAllText(node.getChild(i), list)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAndAccept(root: AccessibilityNodeInfo, pkg: String) {
|
||||
val acceptTerms = listOf("Accept", "قبول", "Tap to accept", "Confirm", "تأكيد")
|
||||
|
||||
for (term in acceptTerms) {
|
||||
val nodes = rootNode.findAccessibilityNodeInfosByText(term)
|
||||
val nodes = root.findAccessibilityNodeInfosByText(term)
|
||||
for (node in nodes) {
|
||||
if (node.isClickable) {
|
||||
// Simulating human delay (e.g., 500ms)
|
||||
handler.postDelayed({
|
||||
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
Log.d("JordanBot", "Clicked Accept on node: ${node.text}")
|
||||
// Reset pending accept after clicking
|
||||
RideNotificationListener.pendingAcceptPackage = null
|
||||
}, 500)
|
||||
if (node.isClickable || node.parent?.isClickable == true) {
|
||||
val target = if (node.isClickable) node else node.parent
|
||||
Log.d("JordanBot", "🤖 Auto-Accepting via Click: $term")
|
||||
target.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pkg == "com.ubercab.driver") {
|
||||
performUberSwipe()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInterrupt() {
|
||||
Log.e("JordanBot", "Accessibility Service Interrupted")
|
||||
private fun performUberSwipe() {
|
||||
Log.d("JordanBot", "🤖 Attempting Uber Swipe...")
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val width = displayMetrics.widthPixels
|
||||
val height = displayMetrics.heightPixels
|
||||
|
||||
val path = Path()
|
||||
path.moveTo(width * 0.2f, height * 0.85f)
|
||||
path.lineTo(width * 0.8f, height * 0.85f)
|
||||
|
||||
val gesture = GestureDescription.Builder()
|
||||
.addStroke(GestureDescription.StrokeDescription(path, 0, 500))
|
||||
.build()
|
||||
|
||||
dispatchGesture(gesture, null, null)
|
||||
}
|
||||
|
||||
override fun onInterrupt() {}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
Log.d("JordanBot", "Accessibility Service Connected")
|
||||
|
||||
RideDataMerger.onRideReady = { mergedRide ->
|
||||
val passesFilters = filterEngine.evaluate(mergedRide)
|
||||
val canAccept = SubscriptionManager.canAcceptRides
|
||||
|
||||
Log.d("JordanBot", "🏁 Merger callback: PassesFilters=$passesFilters, CanAcceptQuota=$canAccept for ${mergedRide.appPackage}")
|
||||
|
||||
if (passesFilters) {
|
||||
if (canAccept) {
|
||||
val rootNode = rootInActiveWindow
|
||||
if (rootNode != null) {
|
||||
findAndAccept(rootNode, mergedRide.appPackage)
|
||||
} else {
|
||||
Log.e("JordanBot", "Cannot accept: rootInActiveWindow is null")
|
||||
}
|
||||
} else {
|
||||
Log.w("JordanBot", "⚠️ Ride matches filters but DAILY LIMIT REACHED. Upgrade required.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val info = AccessibilityServiceInfo().apply {
|
||||
eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED or AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
|
||||
feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC
|
||||
notificationTimeout = 100
|
||||
flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS or AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
|
||||
flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS or AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
|
||||
}
|
||||
this.serviceInfo = info
|
||||
}
|
||||
|
||||
@@ -70,23 +70,11 @@ class RideNotificationListener : NotificationListenerService() {
|
||||
Log.d("JordanBot", "Notification Content: $title | $text")
|
||||
|
||||
val rideRequest = parser.parse(title, text)
|
||||
if (rideRequest == null) {
|
||||
Log.d("JordanBot", "Parser failed to extract ride info from: $title | $text")
|
||||
}
|
||||
|
||||
if (rideRequest != null) {
|
||||
val isAccepted = filterEngine.evaluate(rideRequest)
|
||||
|
||||
// Send to Backend for Data Mining
|
||||
sendLogToBackend(rideRequest, isAccepted, "$title - $text")
|
||||
|
||||
if (isAccepted) {
|
||||
Log.d("JordanBot", "Ride ACCEPTED by filter: $rideRequest")
|
||||
pendingAcceptPackage = packageName
|
||||
lastRequestTime = System.currentTimeMillis()
|
||||
} else {
|
||||
Log.d("JordanBot", "Ride REJECTED by filter: $rideRequest")
|
||||
}
|
||||
// Forward to Merger instead of processing here
|
||||
RideDataMerger.updateFromNotification(rideRequest)
|
||||
} else {
|
||||
Log.d("JordanBot", "Parser failed to extract ride info from: $title | $text")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.jordanbot.autoride.subscription
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.jordanbot.autoride.api.ApiClient
|
||||
import com.jordanbot.autoride.api.CheckSubscriptionRequest
|
||||
import com.jordanbot.autoride.utils.DeviceUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object SubscriptionManager {
|
||||
private const val PREFS_NAME = "SubscriptionPrefs"
|
||||
private const val KEY_PLAN = "plan"
|
||||
private const val KEY_CAN_ACCEPT = "can_accept"
|
||||
private const val KEY_RIDES_TODAY = "rides_today"
|
||||
private const val KEY_RIDES_LIMIT = "rides_limit"
|
||||
|
||||
var currentPlan: String = "free"
|
||||
var canAcceptRides: Boolean = true
|
||||
var ridesToday: Int = 0
|
||||
var ridesLimit: Int = 1
|
||||
|
||||
fun loadLocalCache(context: Context) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
currentPlan = prefs.getString(KEY_PLAN, "free") ?: "free"
|
||||
canAcceptRides = prefs.getBoolean(KEY_CAN_ACCEPT, true)
|
||||
ridesToday = prefs.getInt(KEY_RIDES_TODAY, 0)
|
||||
ridesLimit = prefs.getInt(KEY_RIDES_LIMIT, 1)
|
||||
}
|
||||
|
||||
suspend fun checkSubscription(context: Context): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val fingerprint = DeviceUtils.getDeviceFingerprint(context)
|
||||
val response = ApiClient.service.checkSubscription(CheckSubscriptionRequest(fingerprint))
|
||||
|
||||
if (response.success) {
|
||||
currentPlan = response.plan ?: "free"
|
||||
canAcceptRides = response.can_accept ?: false
|
||||
ridesToday = response.rides_today ?: 0
|
||||
ridesLimit = response.rides_limit ?: 1
|
||||
|
||||
// Save to cache
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().apply {
|
||||
putString(KEY_PLAN, currentPlan)
|
||||
putBoolean(KEY_CAN_ACCEPT, canAcceptRides)
|
||||
putInt(KEY_RIDES_TODAY, ridesToday)
|
||||
putInt(KEY_RIDES_LIMIT, ridesLimit)
|
||||
apply()
|
||||
}
|
||||
Log.d("JordanBot", "Subscription checked: $currentPlan, canAccept: $canAcceptRides")
|
||||
return@withContext true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("JordanBot", "Failed to check subscription", e)
|
||||
}
|
||||
return@withContext false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user