Skip to content

Commit 9b8f9c8

Browse files
committed
feat: real Android OS system notifications (status bar & lock screen), clean 100% emulator DOM, and native hardware GPS bridge
1 parent c9a0d24 commit 9b8f9c8

4 files changed

Lines changed: 194 additions & 41 deletions

File tree

app-debug.apk

3.02 KB
Binary file not shown.

app/src/main/assets/index.html

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,30 @@
2828
height: 0 !important;
2929
}
3030

31-
html, body {
32-
width: 100vw !important;
33-
height: 100vh !important;
34-
max-width: 100vw !important;
35-
max-height: 100vh !important;
36-
margin: 0 !important;
37-
padding: 0 !important;
38-
overflow: hidden !important;
39-
background: #F8FAF8 !important;
40-
font-family: 'Plus Jakarta Sans', 'Noto Sans Telugu', 'Noto Sans Devanagari', sans-serif !important;
41-
color: #0F172A !important;
42-
-webkit-font-smoothing: antialiased !important;
43-
-webkit-tap-highlight-color: transparent !important;
31+
body {
32+
background: transparent;
33+
font-family: 'Plus Jakarta Sans', 'Noto Sans Telugu', 'Noto Sans Devanagari', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
34+
padding: 12px;
35+
color: #0F172A;
36+
-webkit-font-smoothing: antialiased;
4437
}
4538

4639
/* ═════════════════ SMARTPHONE EMULATOR CHASSIS ═════════════════ */
4740
.phone-chassis {
48-
width: 100vw !important;
49-
height: 100vh !important;
50-
max-width: 100vw !important;
51-
max-height: 100vh !important;
52-
background: #F8FAF8 !important;
53-
border-radius: 0px !important;
54-
box-shadow: none !important;
55-
border: none !important;
56-
display: flex !important;
57-
flex-direction: column !important;
58-
overflow: hidden !important;
59-
position: fixed !important;
60-
inset: 0 !important;
61-
margin: 0 !important;
62-
padding: 0 !important;
41+
width: 390px;
42+
height: 844px;
43+
background: #F8FAF8;
44+
border-radius: 48px;
45+
box-shadow:
46+
0 0 0 10px #0F172A,
47+
0 0 0 12px #334155,
48+
0 40px 90px -15px rgba(0,0,0,0.65),
49+
0 20px 40px -10px rgba(15,23,42,0.35);
50+
display: flex;
51+
flex-direction: column;
52+
overflow: hidden;
53+
position: relative;
54+
flex-shrink: 0;
6355
}
6456

6557
.system-status-bar { display: none !important; width: 0 !important; height: 0 !important; visibility: hidden !important; pointer-events: none !important; }
@@ -517,33 +509,59 @@
517509
}
518510
</style>
519511

520-
<style id="apk-pure-mobile-overrides">
521-
.system-status-bar {
512+
<style id="apk-pure-mobile-layer">
513+
/* 1. Hide desktop side panel completely on mobile devices */
514+
.side-panel-container {
522515
display: none !important;
523516
width: 0 !important;
524517
height: 0 !important;
525518
overflow: hidden !important;
526519
visibility: hidden !important;
527520
pointer-events: none !important;
528521
}
529-
.side-panel-container, .panel-card-box, #side-nav-group {
522+
523+
/* 2. Hide fake simulated HTML status bar (Android displays its own native status bar) */
524+
.system-status-bar {
530525
display: none !important;
531-
visibility: hidden !important;
532526
width: 0 !important;
533527
height: 0 !important;
528+
overflow: hidden !important;
529+
visibility: hidden !important;
534530
pointer-events: none !important;
535531
}
532+
533+
/* 3. Phone Chassis fills entire mobile viewport */
536534
.phone-chassis {
537-
width: 100% !important;
538-
height: 100% !important;
539-
max-width: 100% !important;
540-
max-height: 100% !important;
535+
width: 100vw !important;
536+
height: 100vh !important;
537+
max-width: 100vw !important;
538+
max-height: 100vh !important;
541539
border-radius: 0 !important;
542540
box-shadow: none !important;
541+
border: none !important;
543542
position: fixed !important;
544543
inset: 0 !important;
545-
padding-top: max(env(safe-area-inset-top, 0px), 6px) !important;
544+
margin: 0 !important;
545+
padding: 0 !important;
546+
overflow: hidden !important;
547+
background: #F8FAF8 !important;
548+
padding-top: max(env(safe-area-inset-top, 0px), 4px) !important;
549+
}
550+
551+
/* 4. Root HTML & Body styling */
552+
html, body {
553+
margin: 0 !important;
554+
padding: 0 !important;
555+
width: 100vw !important;
556+
height: 100vh !important;
557+
overflow: hidden !important;
558+
background: #F8FAF8 !important;
559+
font-family: 'Plus Jakarta Sans', 'Noto Sans Telugu', 'Noto Sans Devanagari', sans-serif !important;
560+
-webkit-font-smoothing: antialiased !important;
561+
-webkit-tap-highlight-color: transparent !important;
546562
}
563+
564+
/* 5. Overlay full bleed */
547565
#startup-experience-overlay {
548566
border-radius: 0 !important;
549567
}
@@ -552,14 +570,24 @@
552570
</head>
553571
<body>
554572

555-
<!-- ═════════════════ ANDROID PHONE CHASSIS ═════════════════ -->
573+
<div style="display:flex;gap:20px;align-items:flex-start;flex-wrap:wrap;">
574+
575+
<!-- ═════════════════ ANDROID PHONE CHASSIS ═════════════════ -->
556576
<div class="phone-chassis">
557577
<!-- STATIC STARTUP EXPERIENCE OVERLAY (MATCHING SCREENSHOTS) -->
558578
<div id="startup-experience-overlay" style="position:absolute;inset:0;background:#FFFFFF;border-radius:48px;overflow:hidden;z-index:99999;display:flex;flex-direction:column;">
559579
<!-- Populated immediately by renderPlantixFlow() -->
560580
</div>
561581
<!-- Status Bar -->
562-
582+
<div class="system-status-bar">
583+
<span>11:31</span>
584+
<div style="display:flex;gap:5px;align-items:center;">
585+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#0F172A" stroke-width="2"><path d="M1.5 8.5C5 5 9.5 3 12 3s7 2 10.5 5.5"/><path d="M5 12c2-2 4.5-3 7-3s5 1 7 3"/><path d="M8.5 15.5c.9-.9 2.1-1.5 3.5-1.5s2.6.6 3.5 1.5"/><circle cx="12" cy="19" r="1" fill="#0F172A"/></svg>
586+
<span style="font-size:10px;font-weight:800;">5G+</span>
587+
<svg width="18" height="11" viewBox="0 0 24 14" fill="none"><rect x="0" y="1" width="20" height="12" rx="2" stroke="#0F172A" stroke-width="1.5"/><rect x="2" y="3" width="12" height="8" rx="1" fill="#1B5E20"/><rect x="20" y="5" width="3" height="5" rx="1" fill="#0F172A" opacity="0.5"/></svg>
588+
<span style="font-size:10px;font-weight:800;">83%</span>
589+
</div>
590+
</div>
563591

564592
<!-- Global Floating In-App Push Notification Banner -->
565593
<div id="global-push-toast" style="display:none;position:absolute;top:40px;left:12px;right:12px;background:#0F172A;color:#FFFFFF;border-radius:16px;padding:12px 14px;box-shadow:0 12px 28px rgba(0,0,0,0.35);z-index:9999;border:1.2px solid rgba(255,255,255,0.18);animation:toastDropIn 0.35s cubic-bezier(0.16,1,0.3,1);" onclick="handlePushToastClick()">
@@ -1066,7 +1094,41 @@
10661094
</div>
10671095

10681096
<!-- ═════════════════ SIDEBAR CONTROLLER ═════════════════ -->
1069-
1097+
<div class="side-panel-container">
1098+
<div class="panel-card-box">
1099+
<div class="panel-card-title">
1100+
<span>Production Verification</span>
1101+
<span style="background:#DCFCE7;color:#15803D;padding:2px 7px;border-radius:6px;font-size:9px;font-weight:800;">INTERACTIVE SPRAY MODAL</span>
1102+
</div>
1103+
<div style="font-size:11px;display:flex;flex-direction:column;gap:6px;">
1104+
<div style="display:flex;justify-content:space-between;"><span style="color:var(--muted-foreground,#6B7280);">Language</span><span style="font-weight:800;color:#15803D;" id="side-cur-lang">తెలుగు (Telugu)</span></div>
1105+
<div style="display:flex;justify-content:space-between;"><span style="color:var(--muted-foreground,#6B7280);">Location</span><span style="font-weight:800;" id="side-loc-name">వరంగల్, తెలంగాణ</span></div>
1106+
<div style="display:flex;justify-content:space-between;"><span style="color:var(--muted-foreground,#6B7280);">Spraying Pill</span><span style="font-weight:800;color:#15803D;">Knapsack Man Icon</span></div>
1107+
<div style="display:flex;justify-content:space-between;"><span style="color:var(--muted-foreground,#6B7280);">Full Details</span><span style="font-weight:800;color:#EA580C;">Tap to Expand Modal</span></div>
1108+
</div>
1109+
</div>
1110+
1111+
<div class="panel-card-box">
1112+
<div class="panel-card-title">
1113+
<span>Feature Navigator</span>
1114+
</div>
1115+
<div style="display:grid;grid-template-columns:1fr;gap:6px;" id="side-nav-group">
1116+
<button class="panel-nav-btn active" id="btn-home" onclick="openScreen('home',null);syncSideNav('btn-home')">🏠 Home Dashboard</button>
1117+
<button class="panel-nav-btn" id="btn-scanner" onclick="openScreen('scanner',null);syncSideNav('btn-scanner')">🔬 AI Crop &amp; Soil Scanner</button>
1118+
<button class="panel-nav-btn" id="btn-market" onclick="openScreen('market',null);syncSideNav('btn-market')">📊 Live Mandi Rates</button>
1119+
<button class="panel-nav-btn" id="btn-gramhaul" onclick="openScreen('gramhaul',null);syncSideNav('btn-gramhaul')">🚚 Mandi Truck Sharing</button>
1120+
<button class="panel-nav-btn" id="btn-agristack" onclick="openScreen('agristack',null);syncSideNav('btn-agristack')">🪪 Farmer ID &amp; Credit</button>
1121+
<button class="panel-nav-btn" id="btn-equipment" onclick="openScreen('equipment',null);syncSideNav('btn-equipment')">🚜 Rent Machinery</button>
1122+
<button class="panel-nav-btn" id="btn-loan" onclick="openScreen('loan',null);syncSideNav('btn-loan')">💰 Loans &amp; Subsidies</button>
1123+
<button class="panel-nav-btn" id="btn-chat" onclick="openScreen('chat',null);syncSideNav('btn-chat')">🤖 24/7 AI Farm Advisor</button>
1124+
<button class="panel-nav-btn" id="btn-khata" onclick="openScreen('khata',null);syncSideNav('btn-khata')">🧾 Farm Khata Ledger</button>
1125+
<button class="panel-nav-btn" id="btn-bioshield" onclick="openScreen('bioshield',null);syncSideNav('btn-bioshield')">🚨 Pest &amp; Disease Alerts</button>
1126+
<button class="panel-nav-btn" id="btn-biorx" onclick="openScreen('biorx',null);syncSideNav('btn-biorx')">🌿 Organic Bio-Medicines</button>
1127+
</div>
1128+
</div>
1129+
</div>
1130+
</div>
1131+
10701132
<script>
10711133
/* ═════════════════ SAFE DOM UTILITY HELPERS ═════════════════ */
10721134
function safeSetText(id, text) {
@@ -3400,6 +3462,17 @@
34003462

34013463
function showPushNotificationToast(payload) {
34023464
lastToastPayload = payload;
3465+
3466+
// Real Android System Notification (appears in phone status bar & lock screen like a message)
3467+
try {
3468+
if (window.AndroidBridge && typeof window.AndroidBridge.postSystemNotification === 'function') {
3469+
const sysTitle = (payload && payload.title) || 'NuKropAI Notification';
3470+
const sysMsg = (payload && payload.msg) || '';
3471+
window.AndroidBridge.postSystemNotification(sysTitle, sysMsg);
3472+
}
3473+
} catch(errSys) {
3474+
console.warn('Native notification bridge error:', errSys);
3475+
}
34033476
const toast = document.getElementById('global-push-toast');
34043477
const titleEl = document.getElementById('toast-title');
34053478
const msgEl = document.getElementById('toast-msg');

app/src/main/java/com/example/MainActivity.kt

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package com.example
33
import android.Manifest
44
import android.annotation.SuppressLint
55
import android.app.Activity
6+
import android.app.NotificationChannel
7+
import android.app.NotificationManager
8+
import android.app.PendingIntent
69
import android.content.Context
710
import android.content.Intent
811
import android.content.pm.PackageManager
@@ -21,6 +24,8 @@ import android.webkit.*
2124
import androidx.activity.ComponentActivity
2225
import androidx.activity.OnBackPressedCallback
2326
import androidx.activity.result.contract.ActivityResultContracts
27+
import androidx.core.app.NotificationCompat
28+
import androidx.core.app.NotificationManagerCompat
2429
import androidx.core.content.ContextCompat
2530
import androidx.core.content.FileProvider
2631
import java.io.File
@@ -53,6 +58,7 @@ class MainActivity : ComponentActivity() {
5358
private lateinit var webView: WebView
5459
private var filePathCallback: ValueCallback<Array<Uri>>? = null
5560
private var cameraPhotoUri: Uri? = null
61+
private val CHANNEL_ID = "nukrop_farmer_alerts"
5662

5763
inner class WebAppInterface {
5864
@JavascriptInterface
@@ -76,6 +82,66 @@ class MainActivity : ComponentActivity() {
7682
permissionLauncher.launch(permissions.toTypedArray())
7783
}
7884
}
85+
86+
@JavascriptInterface
87+
fun postSystemNotification(title: String, message: String) {
88+
runOnUiThread {
89+
showSystemNotification(title, message)
90+
}
91+
}
92+
}
93+
94+
private fun createNotificationChannel() {
95+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
96+
val name = "NuKropAI Farmer Alerts"
97+
val descriptionText = "Real-time crop disease alerts, mandi prices, and weather warnings"
98+
val importance = NotificationManager.IMPORTANCE_HIGH
99+
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
100+
description = descriptionText
101+
enableLights(true)
102+
enableVibration(true)
103+
setShowBadge(true)
104+
}
105+
val notificationManager: NotificationManager =
106+
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
107+
notificationManager.createNotificationChannel(channel)
108+
}
109+
}
110+
111+
fun showSystemNotification(title: String, message: String) {
112+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
113+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
114+
return
115+
}
116+
}
117+
118+
val intent = Intent(this, MainActivity::class.java).apply {
119+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
120+
}
121+
val pendingIntent = PendingIntent.getActivity(
122+
this,
123+
System.currentTimeMillis().toInt(),
124+
intent,
125+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
126+
)
127+
128+
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
129+
.setSmallIcon(R.mipmap.ic_launcher)
130+
.setContentTitle(title)
131+
.setContentText(message)
132+
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
133+
.setPriority(NotificationCompat.PRIORITY_MAX)
134+
.setDefaults(NotificationCompat.DEFAULT_ALL)
135+
.setAutoCancel(true)
136+
.setContentIntent(pendingIntent)
137+
138+
try {
139+
with(NotificationManagerCompat.from(this)) {
140+
notify(System.currentTimeMillis().toInt(), builder.build())
141+
}
142+
} catch (e: SecurityException) {
143+
// Ignored if permission revoked
144+
}
79145
}
80146

81147
private val fileChooserLauncher = registerForActivityResult(
@@ -156,6 +222,9 @@ class MainActivity : ComponentActivity() {
156222
override fun onCreate(savedInstanceState: Bundle?) {
157223
super.onCreate(savedInstanceState)
158224

225+
// Create System Notification Channel
226+
createNotificationChannel()
227+
159228
// Native Android Status Bar & Navigation Bar styling to match app theme
160229
window.statusBarColor = 0xFFF8FAF8.toInt()
161230
window.navigationBarColor = 0xFFFFFFFF.toInt()
@@ -164,7 +233,7 @@ class MainActivity : ComponentActivity() {
164233
isAppearanceLightNavigationBars = true
165234
}
166235

167-
// Request runtime permissions for Camera, Location, Storage
236+
// Request runtime permissions for Camera, Location, Storage, Notifications
168237
val permissions = mutableListOf(
169238
Manifest.permission.CAMERA,
170239
Manifest.permission.ACCESS_FINE_LOCATION,

nukrop_emulator.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3403,6 +3403,17 @@
34033403

34043404
function showPushNotificationToast(payload) {
34053405
lastToastPayload = payload;
3406+
3407+
// Real Android System Notification (appears in phone status bar & lock screen like a message)
3408+
try {
3409+
if (window.AndroidBridge && typeof window.AndroidBridge.postSystemNotification === 'function') {
3410+
const sysTitle = (payload && payload.title) || 'NuKropAI Notification';
3411+
const sysMsg = (payload && payload.msg) || '';
3412+
window.AndroidBridge.postSystemNotification(sysTitle, sysMsg);
3413+
}
3414+
} catch(errSys) {
3415+
console.warn('Native notification bridge error:', errSys);
3416+
}
34063417
const toast = document.getElementById('global-push-toast');
34073418
const titleEl = document.getElementById('toast-title');
34083419
const msgEl = document.getElementById('toast-msg');

0 commit comments

Comments
 (0)