initial commit: push everything
49
.gitignore
vendored
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||||
|
}
|
||||||
59
android/app/build.gradle.kts
Normal 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") }
|
||||||
163
android/app/google-services.json
Normal 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"
|
||||||
|
}
|
||||||
BIN
android/app/nearlerider-keystore.jks
Normal file
89
android/app/proguard-rules.pro
vendored
Normal 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
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal 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>
|
||||||
144
android/app/src/main/AndroidManifest.xml
Normal 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>
|
||||||
315
android/app/src/main/kotlin/com/nearle/gear/MainActivity.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
278
android/app/src/main/kotlin/com/nearle/gear/ShiftEndReceiver.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
android/app/src/main/res/drawable-hdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
android/app/src/main/res/drawable-mdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
android/app/src/main/res/drawable-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
android/app/src/main/res/drawable-night-hdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
android/app/src/main/res/drawable-night-mdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 121 B |
|
After Width: | Height: | Size: 239 B |
|
After Width: | Height: | Size: 358 B |
|
After Width: | Height: | Size: 468 B |
BIN
android/app/src/main/res/drawable-v21/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -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>
|
||||||
BIN
android/app/src/main/res/drawable-xhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 239 B |
BIN
android/app/src/main/res/drawable-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 239 B |
BIN
android/app/src/main/res/drawable-xxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 358 B |
BIN
android/app/src/main/res/drawable-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 358 B |
BIN
android/app/src/main/res/drawable-xxxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 468 B |
BIN
android/app/src/main/res/drawable-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 468 B |
BIN
android/app/src/main/res/drawable/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
9
android/app/src/main/res/drawable/launch_background.xml
Normal 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>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/raw/alert_1.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_10.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_2.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_3.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_4.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_5.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_6.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_7.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_8.mp3
Normal file
BIN
android/app/src/main/res/raw/alert_9.mp3
Normal file
BIN
android/app/src/main/res/raw/destination.mp3
Normal file
22
android/app/src/main/res/values-night-v31/styles.xml
Normal 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>
|
||||||
22
android/app/src/main/res/values-night/styles.xml
Normal 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>
|
||||||
22
android/app/src/main/res/values-v31/styles.xml
Normal 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>
|
||||||
6
android/app/src/main/res/values/styles.xml
Normal 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>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal 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
@@ -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)
|
||||||
|
}
|
||||||
7
android/gradle.properties
Normal 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
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
@@ -0,0 +1,4 @@
|
|||||||
|
storePassword=123456789
|
||||||
|
keyPassword=123456789
|
||||||
|
keyAlias= nearle
|
||||||
|
storeFile=nearlerider-keystore.jks
|
||||||
27
android/settings.gradle.kts
Normal 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
BIN
assets/audio/alert-10.mp3
Normal file
BIN
assets/audio/alert-2.mp3
Normal file
BIN
assets/audio/alert-3.mp3
Normal file
BIN
assets/audio/alert-4.mp3
Normal file
BIN
assets/audio/alert-5.mp3
Normal file
BIN
assets/audio/alert-6.mp3
Normal file
BIN
assets/audio/alert-7.mp3
Normal file
BIN
assets/audio/alert-8.mp3
Normal file
BIN
assets/audio/alert-9.mp3
Normal file
BIN
assets/audio/destination.mp3
Normal file
BIN
assets/fonts/ProximaNova/proximanova_bold.otf
Normal file
BIN
assets/fonts/ProximaNova/proximanova_regular.ttf
Normal file
BIN
assets/images/CreateMpin.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
assets/images/Gps.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
assets/images/Mpin.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
assets/images/Nearle Bike.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
BIN
assets/images/cancel.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/images/customermap.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/deliveryicon.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/images/gps-navigation.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/homeicon.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
assets/images/in.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/images/information-point.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/images/intro1.png
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
assets/images/intro2.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
assets/images/intro3.png
Normal file
|
After Width: | Height: | Size: 744 KiB |
BIN
assets/images/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
assets/images/map.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/images/nearlelauncher.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/images/nearleplaystore1.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
assets/images/nearlesplash2.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/images/onlinebottom.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
assets/images/onlineoffline.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/images/orderssample.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/images/pending.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/images/phone-call .png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
assets/images/profileicon.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/images/selcart.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/images/selecteddelivery.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/images/selectedprofile.png
Normal file
|
After Width: | Height: | Size: 17 KiB |