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

49
.gitignore vendored Normal file
View File

@@ -0,0 +1,49 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Claude AI settings
.claude/

30
.metadata Normal file
View File

@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: web
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

16
README.md Normal file
View File

@@ -0,0 +1,16 @@
# nearle
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

32
analysis_options.yaml Normal file
View File

@@ -0,0 +1,32 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
file_names: ignore
unused_field: ignore
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore

122
android/app/build.gradle Normal file
View File

@@ -0,0 +1,122 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
id "com.google.gms.google-services"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode") ?: "1"
def flutterVersionName = localProperties.getProperty("flutter.versionName") ?: "1.0"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
def googleMapsApiKey = localProperties.getProperty("GOOGLE_MAPS_API_KEY") ?: ""
android {
namespace = "com.nearle.partner"
compileSdk = 36
// Correct NDK version
ndkVersion "27.0.12077973"
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
// ❌ removed doNotStrip (this was breaking your AAB build)
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled true
}
kotlinOptions {
jvmTarget = "1.8"
}
defaultConfig {
applicationId = "com.nearle.partner"
minSdkVersion flutter.minSdkVersion
targetSdkVersion 36
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders = [
GOOGLE_MAPS_API_KEY: googleMapsApiKey,
applicationName: "android.app.Application"
]
// Build only ARM64
ndk {
abiFilters "arm64-v8a"
}
}
signingConfigs {
release {
keyAlias keystoreProperties["keyAlias"]
keyPassword keystoreProperties["keyPassword"]
storeFile keystoreProperties["storeFile"] ? file(keystoreProperties["storeFile"]) : null
storePassword keystoreProperties["storePassword"]
}
}
buildTypes {
debug {
minifyEnabled false
debuggable true
}
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
// ❌ removed ndk { debugSymbolLevel } (this caused strip crash)
}
}
}
dependencies {
implementation "androidx.core:core:1.10.0"
implementation "com.google.android.gms:play-services-location:21.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.5"
}
flutter {
source = "../.."
}
tasks.register("copyNotificationSounds", Copy) {
def audioDir = file("$projectDir/../../assets/audio")
from(audioDir)
include("*.mp3")
into("$projectDir/src/main/res/raw")
rename { String fileName ->
fileName.replaceAll(/[^A-Za-z0-9_.]/, "_").toLowerCase()
}
}
tasks.named("preBuild").configure {
dependsOn("copyNotificationSounds")
}

View File

@@ -0,0 +1,59 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.nearle"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.nearle"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
packaging {
jniLibs {
doNotStrip.add("**/*.so")
}
}
}
flutter {
source = "../.."
}
// android/app/build.gradle.kts (keep this task)
tasks.register<Copy>("copyNotificationSounds") {
from("$projectDir/../../assets/audio") {
include("*.mp3")
}
into("$projectDir/src/main/res/raw")
rename { it.replace(Regex("[^A-Za-z0-9_.]"), "_").lowercase() }
}
tasks.named("preBuild") { dependsOn("copyNotificationSounds") }

View File

@@ -0,0 +1,163 @@
{
"project_info": {
"project_number": "140444764229",
"firebase_url": "https://nearle-gear-default-rtdb.firebaseio.com",
"project_id": "nearle-gear",
"storage_bucket": "nearle-gear.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:140444764229:android:a584c5e9127c3789283b2c",
"android_client_info": {
"package_name": "com.nearle.admin"
}
},
"oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBkzz2Yua74Q9YpzGmUPFP94fmJQqNMIiU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "140444764229-m6l2v6eofrb9jgrno3qmsrjbtd4iccvd.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.nearle.gear"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:140444764229:android:a99eaae950fa5ada283b2c",
"android_client_info": {
"package_name": "com.nearle.bond"
}
},
"oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBkzz2Yua74Q9YpzGmUPFP94fmJQqNMIiU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "140444764229-m6l2v6eofrb9jgrno3qmsrjbtd4iccvd.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.nearle.gear"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:140444764229:android:88df9627e2990ef4283b2c",
"android_client_info": {
"package_name": "com.nearle.gear"
}
},
"oauth_client": [
{
"client_id": "140444764229-5cllr5f8u28psf8ttc220f6h487u9vjq.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.nearle.gear",
"certificate_hash": "1d8115901ded2af41250ffbfe99b9239a1953c05"
}
},
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBkzz2Yua74Q9YpzGmUPFP94fmJQqNMIiU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "140444764229-m6l2v6eofrb9jgrno3qmsrjbtd4iccvd.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.nearle.gear"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:140444764229:android:578383f5a1d3a05c283b2c",
"android_client_info": {
"package_name": "com.nearle.partner"
}
},
"oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBkzz2Yua74Q9YpzGmUPFP94fmJQqNMIiU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "140444764229-seu4nkl2k2hj6gebk3q20sv80k685ecp.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "140444764229-m6l2v6eofrb9jgrno3qmsrjbtd4iccvd.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.nearle.gear"
}
}
]
}
}
}
],
"configuration_version": "1"
}

Binary file not shown.

89
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,89 @@
##########################################
## FLUTTER
##########################################
-keep class io.flutter.** { *; }
-dontwarn io.flutter.**
##########################################
## FIREBASE (Safe)
##########################################
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }
-dontwarn com.google.firebase.**
-dontwarn com.google.android.gms.**
##########################################
## FCM Background Messaging
##########################################
-keep class com.google.firebase.messaging.FirebaseMessagingService { *; }
-keep class com.google.firebase.messaging.FirebaseMessaging { *; }
-keep class com.google.firebase.messaging.RemoteMessage { *; }
##########################################
## Gson / JSON Models (Correct way)
##########################################
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Keep all model classes to prevent crashes
-keep class **.model.** { *; }
-keep class **.models.** { *; }
##########################################
## Retrofit / OkHttp
##########################################
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn retrofit2.**
-keep class okhttp3.** { *; }
-keep class okio.** { *; }
-keep interface retrofit2.** { *; }
##########################################
## Glide / image loading (avoid BigPicture FCM crash)
##########################################
-dontwarn com.bumptech.glide.**
-keep class com.bumptech.glide.** { *; }
##########################################
## Prevent crashes for reflection
##########################################
-keepattributes Signature
-keepattributes Annotation
##########################################
## KEEP ENUMS (Firebase uses it)
##########################################
-keepclassmembers enum * { *; }
##########################################
## Do NOT break Kotlin (Important)
##########################################
-keep class kotlin.** { *; }
-dontwarn kotlin.**
##########################################
## GETX (State Management & Snackbars)
##########################################
# Keep all GetX classes and methods
-keep class get.** { *; }
-keep class * extends get.GetxController { *; }
-keepclassmembers class * extends get.GetxController {
<methods>;
<fields>;
}
# Keep GetX navigation and routing
-keep class get.Get { *; }
-keep class get.GetMaterialApp { *; }
-keep class get.GetNavigator { *; }
-keep class get.GetSnackbar { *; }
# Keep GetX observables and reactive variables
-keep class get.Rx* { *; }
-keep class get.Obs* { *; }
-dontwarn get.**
##########################################
## Reduce unnecessary warnings
##########################################
-ignorewarnings

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,144 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ===== Permissions ===== -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- For older Android versions -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE" />
<!-- Android 13+ notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Foreground service for background logging -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Optional: declare types for Android 14+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Location permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Optional: needed for background tracking (break auto-refresh, etc.) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Phone call (for tap-to-call customer / pickup) -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- Alarm permissions for shift end (works even when app is killed) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- ===== Application ===== -->
<application
android:label="Nearle Rider"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"> <!-- allows http if any -->
<!-- Google Maps API Key -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q" />
<!-- Firebase notification channel -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="Nearle" />
<!-- Foreground task notification channel (for flutter_foreground_task) -->
<meta-data
android:name="com.pravera.flutter_foreground_task.notification_channel_name"
android:value="Background Service" />
<meta-data
android:name="com.pravera.flutter_foreground_task.notification_channel_description"
android:value="Keeps Nearle online updates running in background." />
<!-- Foreground service component for flutter_foreground_task -->
<!-- Using dataSync only - location is accessed separately and doesn't need to be part of foreground service type -->
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:exported="false"
android:foregroundServiceType="dataSync|location" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Theme shown before Flutter UI loads -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Required for Flutter plugin registration -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- Shift End BroadcastReceiver (works even when app is killed) -->
<receiver
android:name=".ShiftEndReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.nearle.partner.SHIFT_END_ALARM" />
</intent-filter>
</receiver>
<!-- Optional: Legacy HTTP support (useful for older plugins) -->
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
</application>
<!-- ===== Package visibility (Android 11+) ===== -->
<queries>
<!-- Existing text processing visibility -->
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
<!-- Allow dialer / phone apps for tel: links (Android 11+) -->
<intent>
<action android:name="android.intent.action.DIAL" />
<data android:scheme="tel" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tel" />
</intent>
<intent>
<action android:name="android.intent.action.CALL" />
<data android:scheme="tel" />
</intent>
<!-- Package visibility for common dialer apps -->
<package android:name="com.android.dialer" />
<package android:name="com.google.android.dialer" />
<package android:name="com.samsung.android.dialer" />
<package android:name="com.miui.dialer" />
<package android:name="com.huawei.dialer" />
<package android:name="com.oneplus.dialer" />
<package android:name="com.coloros.dialer" />
<package android:name="com.oppo.dialer" />
<package android:name="com.vivo.dialer" />
<package android:name="com.realme.dialer" />
</queries>
</manifest>

View File

@@ -0,0 +1,315 @@
package com.nearle.partner
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.Rational
import androidx.core.view.WindowCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.util.Calendar
class MainActivity : FlutterActivity() {
private val channelName = "nearle/pip"
private val shiftEndChannelName = "nearle/shift_end"
// PiP tracking for safe exit on resume
private val PREFS = "app_prefs"
private val KEY_WAS_IN_PIP = "was_in_pip"
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// No automatic PiP entry here only from explicit Flutter calls
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Enable edge-to-edge for Android 15+ (SDK 35+)
if (Build.VERSION.SDK_INT >= 35) {
WindowCompat.setDecorFitsSystemWindows(window, false)
}
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
.setMethodCallHandler { call, result ->
when (call.method) {
"enterPip" -> {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val aspect = Rational(16, 9)
val params = PictureInPictureParams.Builder()
.setAspectRatio(aspect)
.build()
enterPictureInPictureMode(params)
} else {
@Suppress("DEPRECATION")
enterPictureInPictureMode()
}
result.success(true)
} catch (e: Exception) {
result.error("PIP_ERROR", e.message, null)
}
}
"moveToBack" -> {
try {
moveTaskToBack(true)
result.success(true)
} catch (e: Exception) {
result.error("MOVE_BACK_ERROR", e.message, null)
}
}
"hasActiveDeliveries" -> {
try {
val hasActive = checkActiveDeliveries()
result.success(hasActive)
} catch (e: Exception) {
result.error("CHECK_ACTIVE_ERROR", e.message, null)
}
}
"ensureExitPip" -> {
try {
ensureExitPip()
result.success(true)
} catch (e: Exception) {
result.error("ENSURE_EXIT_PIP_ERROR", e.message, null)
}
}
else -> result.notImplemented()
}
}
// Shift end alarm channel
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, shiftEndChannelName)
.setMethodCallHandler { call, result ->
when (call.method) {
"scheduleShiftEndAlarm" -> {
try {
val endTimeStr = call.argument<String>("endTime") ?: ""
val startTimeStr = call.argument<String>("startTime") ?: ""
if (endTimeStr.isNotEmpty()) {
scheduleShiftEndAlarm(endTimeStr, startTimeStr)
result.success(true)
} else {
result.error("INVALID_TIME", "End time is required", null)
}
} catch (e: Exception) {
result.error("ALARM_ERROR", e.message, null)
}
}
"cancelShiftEndAlarm" -> {
try {
cancelShiftEndAlarm()
result.success(true)
} catch (e: Exception) {
result.error("CANCEL_ERROR", e.message, null)
}
}
"handleShiftEnd" -> {
try {
// This is called by the BroadcastReceiver
// We need to call Flutter code to create break log
// Since we're in MainActivity, we can use the existing Flutter engine
// But the receiver might not have access to it, so we'll handle it differently
// The receiver will directly call the background service method
result.success(true)
} catch (e: Exception) {
result.error("HANDLE_ERROR", e.message, null)
}
}
else -> result.notImplemented()
}
}
}
// Let Flutter-side explicitly control when to enter PiP (navigation only)
override fun onUserLeaveHint() {
super.onUserLeaveHint()
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration?
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
// Persist PiP state so we can safely exit it on resume
val prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE)
prefs.edit().putBoolean(KEY_WAS_IN_PIP, isInPictureInPictureMode).apply()
}
override fun onResume() {
super.onResume()
// When user brings app back, force full-screen (no PiP)
ensureExitPip()
}
private fun ensureExitPip() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE)
val wasInPip = prefs.getBoolean(KEY_WAS_IN_PIP, false)
// Clear the persisted flag so we don't auto-re-enter PiP
// Note: Android doesn't provide a direct API to exit PiP programmatically,
// but when user opens app from launcher, it naturally returns to full-screen.
// We just ensure we don't auto-re-enter PiP on resume.
if (wasInPip) {
prefs.edit().putBoolean(KEY_WAS_IN_PIP, false).apply()
}
}
private fun checkActiveDeliveries(): Boolean {
return try {
val prefs: SharedPreferences = getSharedPreferences(
"FlutterSharedPreferences",
Context.MODE_PRIVATE
)
// Only check has_live_deliveries (verified by API)
val hasLive = prefs.getBoolean("flutter.has_live_deliveries", false)
hasLive
} catch (e: Exception) {
false
}
}
private fun scheduleShiftEndAlarm(endTimeStr: String, startTimeStr: String) {
try {
Log.d("MainActivity", "📅 Scheduling shift end alarm - endTime: $endTimeStr, startTime: $startTimeStr")
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, ShiftEndReceiver::class.java).apply {
action = "com.nearle.partner.SHIFT_END_ALARM"
}
val pendingIntent = PendingIntent.getBroadcast(
this,
1001, // Unique request code
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Parse end time (format: HH:mm:ss or HH:mm)
val timeParts = endTimeStr.split(":")
if (timeParts.size < 2) {
Log.e("MainActivity", "❌ Invalid end time format: $endTimeStr")
return
}
val endHour = timeParts[0].toIntOrNull() ?: 0
val endMinute = timeParts[1].toIntOrNull() ?: 0
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, endHour)
set(Calendar.MINUTE, endMinute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
// Handle overnight shifts (if start > end)
if (startTimeStr.isNotEmpty()) {
val startParts = startTimeStr.split(":")
if (startParts.size >= 2) {
val startHour = startParts[0].toIntOrNull() ?: 0
val startMinute = startParts[1].toIntOrNull() ?: 0
if (startHour > endHour || (startHour == endHour && startMinute > endMinute)) {
// Overnight shift - if current time is before end time, schedule for tomorrow
val now = Calendar.getInstance()
if (now.before(calendar)) {
calendar.add(Calendar.DAY_OF_MONTH, 1)
}
}
}
}
// ✅ CRITICAL: If shift end time has already passed today, trigger immediately
val now = Calendar.getInstance()
Log.d("MainActivity", "Current time: ${now.time}, Shift end time: ${calendar.time}")
if (calendar.before(now) || calendar.equals(now)) {
// Shift end time has passed - trigger break log immediately
Log.d("MainActivity", "⚡ Shift end time ($endTimeStr) has already passed - triggering break log immediately")
// Trigger the receiver immediately
val immediateIntent = Intent(this, ShiftEndReceiver::class.java).apply {
action = "com.nearle.partner.SHIFT_END_ALARM"
}
sendBroadcast(immediateIntent)
Log.d("MainActivity", "✅ Sent immediate broadcast to ShiftEndReceiver")
// Also schedule for tomorrow to handle next shift
calendar.add(Calendar.DAY_OF_MONTH, 1)
Log.d("MainActivity", "📅 Also scheduled shift end alarm for tomorrow: ${calendar.time}")
}
// Schedule exact alarm (Android 12+ requires SCHEDULE_EXACT_ALARM permission)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d("MainActivity", "✅ Scheduled exact shift end alarm for: ${calendar.time} (${calendar.timeInMillis})")
} else {
// Fallback to inexact alarm
alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.w("MainActivity", "⚠️ Scheduled inexact shift end alarm (no permission) for: ${calendar.time}")
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d("MainActivity", "✅ Scheduled exact shift end alarm (Android M+) for: ${calendar.time}")
} else {
@Suppress("DEPRECATION")
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d("MainActivity", "✅ Scheduled exact shift end alarm (legacy) for: ${calendar.time}")
}
}
} catch (e: Exception) {
Log.e("MainActivity", "❌ Error scheduling shift end alarm: ${e.message}", e)
e.printStackTrace()
}
}
private fun cancelShiftEndAlarm() {
try {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, ShiftEndReceiver::class.java).apply {
action = "com.nearle.partner.SHIFT_END_ALARM"
}
val pendingIntent = PendingIntent.getBroadcast(
this,
1001,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.cancel(pendingIntent)
Log.d("MainActivity", "Cancelled shift end alarm")
} catch (e: Exception) {
Log.e("MainActivity", "Error cancelling shift end alarm: ${e.message}", e)
}
}
}

View File

@@ -0,0 +1,278 @@
package com.nearle.partner
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.PowerManager
import android.util.Log
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*
/**
* BroadcastReceiver that triggers when shift end alarm fires
* This works even when app is killed
*
* When app is killed, we directly call the API to create break log
* since we can't reliably start Flutter engine
*/
class ShiftEndReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("ShiftEndReceiver", "🔔 Shift end alarm triggered at ${java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.US).format(java.util.Calendar.getInstance().time)}")
// ✅ CRITICAL: Acquire WakeLock to keep device awake during API call
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ShiftEndReceiver::WakeLock")
wakeLock.acquire(60 * 1000L) // Hold for 60 seconds max
// Execute API call in background thread
Thread {
try {
// Check if rider is still on duty
val prefs: SharedPreferences = context.getSharedPreferences(
"FlutterSharedPreferences",
Context.MODE_PRIVATE
)
// ✅ Try multiple key formats (Flutter SharedPreferences uses "flutter." prefix)
val onduty = prefs.getInt("flutter.onduty", 0)
Log.d("ShiftEndReceiver", "Checking onduty: $onduty")
if (onduty != 1) {
Log.d("ShiftEndReceiver", "Rider already offline (onduty=$onduty), skipping break log creation")
return@Thread
}
// Get required IDs - try both flutter.userid and flutter.userId
val userid = prefs.getInt("flutter.userid", 0).takeIf { it != 0 }
?: prefs.getInt("flutter.userId", 0)
val partnerid = prefs.getInt("flutter.partnerid", 0).takeIf { it != 0 }
?: prefs.getInt("flutter.partnerId", 0)
val shiftid = prefs.getInt("flutter.shiftid", 0).takeIf { it != 0 }
?: prefs.getInt("flutter.shiftId", 0)
val logid = prefs.getInt("flutter.logid", 0).takeIf { it != 0 }
?: prefs.getInt("flutter.logId", 0)
Log.d("ShiftEndReceiver", "Retrieved IDs: userid=$userid, partnerid=$partnerid, shiftid=$shiftid, logid=$logid")
if (userid == 0) {
Log.e("ShiftEndReceiver", "❌ Missing userid, cannot create break log. Available keys: ${prefs.all.keys}")
return@Thread
}
// Get API base URL (check if live or dev)
// ✅ Match homepage logic: use createbreaklog (not createbreakriderlog)
val mainRoute = prefs.getString("flutter.mainRoute", "dev") ?: "dev"
val isLive = mainRoute == "live"
Log.d("ShiftEndReceiver", "API Route: $mainRoute (isLive=$isLive)")
val baseUrl = if (isLive) {
"https://jupiter.nearle.app/live/api/v2/partners/createbreaklog"
} else {
"https://jupiter.nearle.app/dev/api/v2/partners/createbreaklog"
}
// Create break log payload
val now = Calendar.getInstance()
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.US)
val breakdate = dateFormat.format(now.time)
val breakstart = timeFormat.format(now.time)
val localBreakId = (System.currentTimeMillis() % 900).toInt() + 100
// ✅ Compact JSON (no extra whitespace) - matches Flutter format
val payload = """{"breakid":$localBreakId,"logid":$logid,"breakdate":"$breakdate","userid":$userid,"partnerid":$partnerid,"shiftid":$shiftid,"breakstart":"$breakstart","breakend":"","breakhours":0.0,"latitude":"0","longitude":"0"}"""
Log.d("ShiftEndReceiver", "📤 Sending break log request to: $baseUrl")
Log.d("ShiftEndReceiver", "📦 Payload: $payload")
// Make API call
val url = URL(baseUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.doOutput = true
connection.doInput = true
connection.useCaches = false
connection.connectTimeout = 15000 // Increased timeout
connection.readTimeout = 15000
Log.d("ShiftEndReceiver", "🔌 Connecting to API...")
// Write payload
val outputStream = connection.outputStream
val writer = OutputStreamWriter(outputStream, "UTF-8")
writer.write(payload)
writer.flush()
writer.close()
Log.d("ShiftEndReceiver", "📨 Request sent, waiting for response...")
val responseCode = connection.responseCode
Log.d("ShiftEndReceiver", "📥 Break log API response: HTTP $responseCode")
// Read response body for debugging
try {
val responseStream = if (responseCode in 200..299) {
connection.inputStream
} else {
connection.errorStream
}
if (responseStream != null) {
val responseBody = responseStream.bufferedReader().use { it.readText() }
Log.d("ShiftEndReceiver", "📄 Response body: $responseBody")
}
} catch (e: Exception) {
Log.w("ShiftEndReceiver", "Could not read response body: ${e.message}")
}
if (responseCode in 200..299) {
Log.d("ShiftEndReceiver", "✅ Break log created successfully (HTTP $responseCode)")
// Set offline locally
prefs.edit()
.putInt("flutter.onduty", 0)
.putBoolean("flutter.online", false)
.apply()
// Also update rider log to set onduty=0
val updateUrl = if (isLive) {
"https://jupiter.nearle.app/live/api/v2/partners/updateriderlog"
} else {
"https://jupiter.nearle.app/dev/api/v2/partners/updateriderlog"
}
// ✅ Compact JSON (no extra whitespace)
val updatePayload = """{"userid":$userid,"onduty":0,"latitude":"0","longitude":"0"}"""
Log.d("ShiftEndReceiver", "📤 Updating rider status to offline")
try {
val updateConnection = URL(updateUrl).openConnection() as HttpURLConnection
updateConnection.requestMethod = "POST"
updateConnection.setRequestProperty("Content-Type", "application/json")
updateConnection.doOutput = true
updateConnection.connectTimeout = 5000
updateConnection.readTimeout = 5000
val updateWriter = OutputStreamWriter(updateConnection.outputStream, "UTF-8")
updateWriter.write(updatePayload)
updateWriter.flush()
updateWriter.close()
if (updateConnection.responseCode in 200..299) {
Log.d("ShiftEndReceiver", "✅ Rider status updated to Offline")
}
updateConnection.disconnect()
} catch (e: Exception) {
Log.e("ShiftEndReceiver", "Error updating rider status: ${e.message}")
}
} else {
// Read error response for debugging
try {
val errorStream = connection.errorStream
if (errorStream != null) {
val errorResponse = errorStream.bufferedReader().use { it.readText() }
Log.e("ShiftEndReceiver", "❌ Failed to create break log: HTTP $responseCode\nError: $errorResponse")
} else {
Log.e("ShiftEndReceiver", "❌ Failed to create break log: HTTP $responseCode")
}
} catch (e: Exception) {
Log.e("ShiftEndReceiver", "❌ Failed to create break log: HTTP $responseCode (Error reading response: ${e.message})")
}
}
connection.disconnect()
// ✅ CRITICAL: Reschedule alarm for tomorrow (so it works every day automatically)
// This ensures the alarm fires every day at shift end time even if app is killed
try {
val endTimeStr = prefs.getString("flutter.endtime", "") ?: ""
val startTimeStr = prefs.getString("flutter.starttime", "") ?: ""
Log.d("ShiftEndReceiver", "Rescheduling alarm - endTime: $endTimeStr, startTime: $startTimeStr")
if (endTimeStr.isNotEmpty()) {
// Schedule alarm for tomorrow at the same time
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, ShiftEndReceiver::class.java).apply {
action = "com.nearle.partner.SHIFT_END_ALARM"
}
val pendingIntent = PendingIntent.getBroadcast(
context,
1001,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val timeParts = endTimeStr.split(":")
if (timeParts.size >= 2) {
val endHour = timeParts[0].toIntOrNull() ?: 0
val endMinute = timeParts[1].toIntOrNull() ?: 0
val calendar = Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, 1) // Tomorrow
set(Calendar.HOUR_OF_DAY, endHour)
set(Calendar.MINUTE, endMinute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d("ShiftEndReceiver", "✅ Rescheduled alarm for tomorrow: ${calendar.time}")
} else {
alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
}
} else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
} else {
@Suppress("DEPRECATION")
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
}
Log.d("ShiftEndReceiver", "✅ Rescheduled alarm for tomorrow: ${calendar.time}")
}
}
}
} catch (e: Exception) {
Log.e("ShiftEndReceiver", "Error rescheduling alarm: ${e.message}", e)
}
} catch (e: Exception) {
Log.e("ShiftEndReceiver", "❌ Error handling shift end: ${e.message}", e)
e.printStackTrace()
} finally {
// ✅ CRITICAL: Release WakeLock in finally block
try {
if (wakeLock.isHeld) {
wakeLock.release()
Log.d("ShiftEndReceiver", "🔓 WakeLock released")
}
} catch (e: Exception) {
Log.e("ShiftEndReceiver", "Error releasing WakeLock: ${e.message}")
}
}
}.start()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#FFFFFF</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#FFFFFF</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/transparent</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

24
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,7 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

4
android/key.properties Normal file
View File

@@ -0,0 +1,4 @@
storePassword=123456789
keyPassword=123456789
keyAlias= nearle
storeFile=nearlerider-keystore.jks

View File

@@ -0,0 +1,27 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
id("com.google.gms.google-services") version "4.4.2" apply false
}
include(":app")

BIN
assets/audio/alert-1.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-10.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-2.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-3.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-4.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-5.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-6.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-7.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-8.mp3 Normal file

Binary file not shown.

BIN
assets/audio/alert-9.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
assets/images/Gps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/images/Mpin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

BIN
assets/images/cancel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/images/homeicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

BIN
assets/images/in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets/images/intro1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

BIN
assets/images/intro2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
assets/images/intro3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
assets/images/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
assets/images/pending.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/images/selcart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Some files were not shown because too many files have changed in this diff Show More