Skip to content

Commit 1b2cb02

Browse files
committed
feat: Add release build script for APK generation
docs: Update design documents to reflect project name change from PeerChat Secure to PeerChat docs: Modify implementation plan to align with project name change and update file paths docs: Revise project state snapshot to reflect name change and recent updates docs: Update requirements document to reflect project name change docs: Revise system architecture document to reflect project name change chore: Update pubspec.lock with new dependencies and versions chore: Update pubspec.yaml to reflect version bump and new dependencies test: Update widget test to reflect project name change fix: Update ADB path error message in smoke test script feat: Enhance website content to include file sharing features and project name change
1 parent f4bd91b commit 1b2cb02

74 files changed

Lines changed: 7522 additions & 1623 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.kiro/specs/mesh-routing/design.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Overview
44

5-
This design specifies a message store-and-forward system with mesh routing capabilities for PeerChat Secure. The system enables multi-hop message delivery through intermediate peers, allowing communication beyond direct connectivity range in disaster scenarios where traditional infrastructure is unavailable.
5+
This design specifies a message store-and-forward system with mesh routing capabilities for PeerChat. The system enables multi-hop message delivery through intermediate peers, allowing communication beyond direct connectivity range in disaster scenarios where traditional infrastructure is unavailable.
66

77
The design integrates with the existing Flutter application architecture, leveraging:
88
- **libsodium (sodium package)** for cryptographic operations (signing, encryption)
@@ -1076,3 +1076,4 @@ A property is a characteristic or behavior that should hold true across all vali
10761076

10771077
**Validates: Requirements 16.3**
10781078

1079+

.kiro/specs/mesh-routing/requirements.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Introduction
44

5-
This document specifies requirements for a message store-and-forward system with mesh routing capabilities for PeerChat Secure, an offline-first disaster communication Flutter application. The system enables messages to travel through multiple devices to reach destinations beyond direct connectivity range, which is critical for disaster scenarios where people are spread out and traditional communication infrastructure is unavailable.
5+
This document specifies requirements for a message store-and-forward system with mesh routing capabilities for PeerChat, an offline-first disaster communication Flutter application. The system enables messages to travel through multiple devices to reach destinations beyond direct connectivity range, which is critical for disaster scenarios where people are spread out and traditional communication infrastructure is unavailable.
66

77
## Glossary
88

@@ -217,3 +217,4 @@ This document specifies requirements for a message store-and-forward system with
217217
3. WHEN a malformed message is received, THE Mesh_Router SHALL discard it and log the error
218218
4. WHEN storage operations fail, THE Mesh_Router SHALL notify the application layer
219219
5. WHEN cryptographic operations fail, THE Mesh_Router SHALL discard the message and log the error
220+

android/app/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ android {
1313
compileOptions {
1414
sourceCompatibility = JavaVersion.VERSION_17
1515
targetCompatibility = JavaVersion.VERSION_17
16+
isCoreLibraryDesugaringEnabled = true
1617
}
1718

1819
kotlinOptions {
@@ -42,3 +43,7 @@ android {
4243
flutter {
4344
source = "../.."
4445
}
46+
47+
dependencies {
48+
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
49+
}

android/app/src/main/AndroidManifest.xml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
2020
android:maxSdkVersion="28" />
2121

22+
<!-- Android 13+ specific media permissions -->
23+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
24+
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
25+
2226
<!-- WiFi Direct permissions -->
2327
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
2428
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
@@ -28,18 +32,26 @@
2832

2933
<!-- Internet permission for mDNS -->
3034
<uses-permission android:name="android.permission.INTERNET" />
35+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
3136
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
3237

3338
<!-- Camera permission for QR code scanning -->
3439
<uses-permission android:name="android.permission.CAMERA" />
3540

41+
<!-- Permission to list installed apps (for App Sharing) -->
42+
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
43+
44+
<!-- Permission to modify system settings (for Hotspot/WiFi optimization) -->
45+
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
46+
47+
3648
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
3749
<uses-feature android:name="android.hardware.wifi" android:required="false" />
3850
<uses-feature android:name="android.hardware.camera" android:required="false" />
3951
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
4052

4153
<application
42-
android:label="PeerChat Secure"
54+
android:label="PeerChat"
4355
android:name="${applicationName}"
4456
android:icon="@mipmap/ic_launcher"
4557
android:requestLegacyExternalStorage="true">
@@ -74,7 +86,7 @@
7486
<!-- Required to query activities that can process text, see:
7587
https://developer.android.com/training/package-visibility and
7688
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
77-
89+
7890
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
7991
<queries>
8092
<intent>

android/app/src/main/kotlin/com/example/peerchat_secure/MainActivity.kt

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,32 @@ package com.example.peerchat_secure
22

33
import android.content.Context
44
import android.content.Intent
5+
import android.content.IntentFilter
56
import android.os.BatteryManager
67
import android.provider.Settings
8+
import android.provider.MediaStore
9+
import android.net.Uri
10+
import android.graphics.Bitmap
11+
import android.graphics.Canvas
12+
import android.graphics.drawable.BitmapDrawable
13+
import android.graphics.drawable.Drawable
14+
import java.io.ByteArrayOutputStream
15+
import android.content.BroadcastReceiver
16+
import android.bluetooth.BluetoothAdapter
717
import io.flutter.embedding.engine.FlutterEngine
818
import io.flutter.embedding.android.FlutterActivity
919
import io.flutter.plugin.common.MethodChannel
20+
import io.flutter.plugin.common.EventChannel
21+
import android.net.wifi.WifiManager
1022

1123
class MainActivity : FlutterActivity() {
1224
companion object {
1325
private const val DEVICE_STATUS_CHANNEL = "peerchat_secure/device_status"
26+
private const val BLUETOOTH_STATE_CHANNEL = "peerchat_secure/bluetooth_state"
1427
}
1528

29+
private var bluetoothReceiver: BroadcastReceiver? = null
30+
1631
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
1732
super.configureFlutterEngine(flutterEngine)
1833

@@ -41,12 +56,216 @@ class MainActivity : FlutterActivity() {
4156
)
4257
}
4358

59+
"getAppIcon" -> {
60+
val packageName = call.argument<String>("packageName")
61+
if (packageName != null) {
62+
try {
63+
val pm = packageManager
64+
val icon = pm.getApplicationIcon(packageName)
65+
val bitmap = drawableToBitmap(icon)
66+
67+
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 128, 128, true)
68+
69+
val stream = ByteArrayOutputStream()
70+
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
71+
result.success(stream.toByteArray())
72+
} catch (e: Exception) {
73+
result.error("ICON_ERROR", e.message, null)
74+
}
75+
} else {
76+
result.error("INVALID_ARGS", "Package name is null", null)
77+
}
78+
}
79+
4480
"openLocationSettings" -> {
4581
try {
4682
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
4783
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
4884
startActivity(intent)
4985
result.success(true)
86+
} catch (e: Exception) {
87+
result.success(null)
88+
}
89+
}
90+
91+
"checkSystemSettingsPermission" -> {
92+
result.success(Settings.System.canWrite(this))
93+
}
94+
95+
"openSystemSettingsPermission" -> {
96+
try {
97+
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
98+
intent.data = android.net.Uri.parse("package:$packageName")
99+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
100+
startActivity(intent)
101+
result.success(true)
102+
} catch (e: Exception) {
103+
result.success(false)
104+
}
105+
}
106+
107+
"toggleBluetooth" -> {
108+
val enable = call.argument<Boolean>("enable") ?: false
109+
try {
110+
val bluetoothAdapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter()
111+
if (bluetoothAdapter != null) {
112+
if (enable) bluetoothAdapter.enable() else bluetoothAdapter.disable()
113+
result.success(true)
114+
} else {
115+
result.success(false)
116+
}
117+
} catch (e: Exception) {
118+
result.error("BT_ERROR", e.message, null)
119+
}
120+
}
121+
122+
"isBluetoothEnabled" -> {
123+
try {
124+
val bluetoothAdapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter()
125+
result.success(bluetoothAdapter?.isEnabled == true)
126+
} catch (e: Exception) {
127+
result.error("BT_STATE_ERROR", e.message, null)
128+
}
129+
}
130+
131+
"isHotspotEnabled" -> {
132+
try {
133+
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
134+
val method = wifiManager.javaClass.getDeclaredMethod("getWifiApState")
135+
val state = method.invoke(wifiManager) as Int
136+
// 13 is WIFI_AP_STATE_ENABLED
137+
result.success(state == 13)
138+
} catch (e: Exception) {
139+
result.success(false)
140+
}
141+
}
142+
143+
"getInstalledApps" -> {
144+
try {
145+
val pm = packageManager
146+
val apps = pm.getInstalledApplications(android.content.pm.PackageManager.GET_META_DATA)
147+
val appList = mutableListOf<Map<String, Any>>()
148+
149+
for (app in apps) {
150+
val launchIntent = pm.getLaunchIntentForPackage(app.packageName)
151+
if (launchIntent != null) {
152+
val label = app.loadLabel(pm).toString()
153+
val apkPath = app.sourceDir
154+
val size = java.io.File(apkPath).length()
155+
156+
val appMap = mapOf(
157+
"name" to label,
158+
"packageName" to app.packageName,
159+
"apkPath" to apkPath,
160+
"size" to size
161+
)
162+
appList.add(appMap)
163+
}
164+
}
165+
result.success(appList)
166+
} catch (e: Exception) {
167+
result.error("APP_LIST_ERROR", e.message, null)
168+
}
169+
}
170+
171+
"checkAllFilesPermission" -> {
172+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
173+
result.success(android.os.Environment.isExternalStorageManager())
174+
} else {
175+
result.success(true)
176+
}
177+
}
178+
179+
"openAllFilesPermission" -> {
180+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
181+
try {
182+
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
183+
intent.data = android.net.Uri.parse("package:$packageName")
184+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
185+
startActivity(intent)
186+
result.success(true)
187+
} catch (e: Exception) {
188+
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
189+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
190+
startActivity(intent)
191+
result.success(true)
192+
}
193+
} else {
194+
result.success(true)
195+
}
196+
}
197+
198+
"getMediaAssets" -> {
199+
val type = call.argument<String>("type") ?: "image"
200+
try {
201+
val mediaList = mutableListOf<Map<String, Any>>()
202+
val projection = arrayOf<String>(
203+
MediaStore.MediaColumns.DISPLAY_NAME,
204+
MediaStore.MediaColumns.DATA,
205+
MediaStore.MediaColumns.SIZE,
206+
MediaStore.MediaColumns.MIME_TYPE,
207+
MediaStore.MediaColumns.DATE_ADDED
208+
)
209+
210+
val uri = if (type == "video") {
211+
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
212+
} else {
213+
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
214+
}
215+
216+
val sortOrder = "${MediaStore.MediaColumns.DATE_ADDED} DESC"
217+
218+
contentResolver.query(uri, projection, null, null, sortOrder)?.use { cursor ->
219+
val nameCol = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
220+
val dataCol = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
221+
val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
222+
val mimeCol = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
223+
224+
var count = 0
225+
while (cursor.moveToNext() && count < 500) {
226+
val path = cursor.getString(dataCol)
227+
if (path != null && java.io.File(path).exists()) {
228+
mediaList.add(mapOf(
229+
"name" to (cursor.getString(nameCol) ?: "Unknown"),
230+
"path" to path,
231+
"size" to cursor.getLong(sizeCol),
232+
"mimeType" to (cursor.getString(mimeCol) ?: "")
233+
))
234+
count++
235+
}
236+
}
237+
}
238+
result.success(mediaList)
239+
} catch (e: Exception) {
240+
result.error("MEDIA_SCAN_ERROR", e.message, null)
241+
}
242+
}
243+
244+
"openHotspotSettings" -> {
245+
try {
246+
val intent = Intent()
247+
intent.action = "android.settings.TETHER_SETTINGS"
248+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
249+
startActivity(intent)
250+
result.success(true)
251+
} catch (e: Exception) {
252+
try {
253+
val intent = Intent(Settings.ACTION_WIRELESS_SETTINGS)
254+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
255+
startActivity(intent)
256+
result.success(true)
257+
} catch (e2: Exception) {
258+
result.success(false)
259+
}
260+
}
261+
}
262+
263+
"openBluetoothSettings" -> {
264+
try {
265+
val intent = Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
266+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
267+
startActivity(intent)
268+
result.success(true)
50269
} catch (e: Exception) {
51270
result.success(false)
52271
}
@@ -55,5 +274,46 @@ class MainActivity : FlutterActivity() {
55274
else -> result.notImplemented()
56275
}
57276
}
277+
278+
EventChannel(flutterEngine.dartExecutor.binaryMessenger, BLUETOOTH_STATE_CHANNEL).setStreamHandler(
279+
object : EventChannel.StreamHandler {
280+
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
281+
bluetoothReceiver = object : BroadcastReceiver() {
282+
override fun onReceive(context: Context?, intent: Intent?) {
283+
if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
284+
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
285+
val isEnabled = (state == BluetoothAdapter.STATE_ON)
286+
events?.success(isEnabled)
287+
}
288+
}
289+
}
290+
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
291+
registerReceiver(bluetoothReceiver, filter)
292+
293+
val adapter = BluetoothAdapter.getDefaultAdapter()
294+
events?.success(adapter?.isEnabled == true)
295+
}
296+
297+
override fun onCancel(arguments: Any?) {
298+
unregisterReceiver(bluetoothReceiver)
299+
bluetoothReceiver = null
300+
}
301+
}
302+
)
303+
}
304+
305+
private fun drawableToBitmap(drawable: Drawable): Bitmap {
306+
if (drawable is BitmapDrawable) {
307+
return drawable.bitmap
308+
}
309+
val bitmap = Bitmap.createBitmap(
310+
drawable.intrinsicWidth.coerceAtLeast(1),
311+
drawable.intrinsicHeight.coerceAtLeast(1),
312+
Bitmap.Config.ARGB_8888
313+
)
314+
val canvas = Canvas(bitmap)
315+
drawable.setBounds(0, 0, canvas.width, canvas.height)
316+
drawable.draw(canvas)
317+
return bitmap
58318
}
59319
}
7.86 KB
Loading
3.82 KB
Loading
12.9 KB
Loading
26.7 KB
Loading
44.9 KB
Loading

0 commit comments

Comments
 (0)