diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index a5a31ce6..9911c273 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -1,15 +1,17 @@
name: Android CI
on:
+ workflow_dispatch:
push:
branches: [ "main", "develop" ]
pull_request:
branches: [ "main", "develop" ]
jobs:
- test:
+ verify:
+ name: Test & Lint
runs-on: ubuntu-latest
-
+
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -39,7 +41,10 @@ jobs:
- name: Run unit tests
run: ./gradlew testDebugUnitTest
- - name: Upload Test Reports (xml+html)
+ - name: Run lint
+ run: ./gradlew lintDebug
+
+ - name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
@@ -48,47 +53,6 @@ jobs:
**/build/test-results/
**/build/reports/tests/
- - name: Upload test results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: test-results
- path: |
- **/build/test-results/
- **/build/reports/tests/
-
- lint:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- java-version: '17'
- distribution: 'temurin'
-
- - name: Setup Gradle
- uses: gradle/gradle-build-action@v3
-
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
-
- - name: Cache Gradle packages
- uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
-
- - name: Run lint
- run: ./gradlew lintDebug
-
- name: Upload lint results
uses: actions/upload-artifact@v4
if: always()
@@ -97,9 +61,13 @@ jobs:
path: '**/build/reports/lint-results-*.html'
build:
+ name: Build ${{ matrix.variant }}
runs-on: ubuntu-latest
- needs: [test, lint]
-
+ needs: verify
+ strategy:
+ matrix:
+ variant: [Debug, Release]
+
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -126,20 +94,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- - name: Build debug APK
- run: ./gradlew assembleDebug
+ - name: Build ${{ matrix.variant }} APK
+ run: ./gradlew assemble${{ matrix.variant }}
- - name: Build release APK
- run: ./gradlew assembleRelease
-
- - name: Upload debug APK
+ - name: Upload ${{ matrix.variant }} APK
uses: actions/upload-artifact@v4
with:
- name: debug-apk
- path: app/build/outputs/apk/debug/*.apk
-
- - name: Upload release APK
- uses: actions/upload-artifact@v4
- with:
- name: release-apk
- path: app/build/outputs/apk/release/*.apk
+ name: ${{ matrix.variant }}-apk
+ path: app/build/outputs/apk/**/*.apk
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4b3b8b7b..f1e1841a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -19,7 +19,7 @@ jobs:
with:
distribution: temurin
java-version: 17
-
+
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
@@ -38,32 +38,32 @@ jobs:
- name: Grant execute permission for Gradlew
run: chmod +x ./gradlew
- - name: Build APK
+ - name: Build Release APKs (with architecture splits)
run: ./gradlew assembleRelease --no-daemon --stacktrace
-
+
- name: List APK files
run: |
echo "APK files built:"
- find app/build/outputs/apk/release -name "*.apk" -type f
+ find app/build/outputs/apk/release -name "*.apk" -type f -exec ls -lh {} \;
- - name: Rename APK
+ - name: Rename APKs for GitHub Release
run: |
- mv app/build/outputs/apk/release/app-release-unsigned.apk app/build/outputs/apk/release/bitchat.apk
-
+ cd app/build/outputs/apk/release
+ [ -f "app-arm64-v8a-release-unsigned.apk" ] && mv app-arm64-v8a-release-unsigned.apk bitchat-android-arm64.apk
+ [ -f "app-x86_64-release-unsigned.apk" ] && mv app-x86_64-release-unsigned.apk bitchat-android-x86_64.apk
+ [ -f "app-universal-release-unsigned.apk" ] && mv app-universal-release-unsigned.apk bitchat-android-universal.apk
+
- name: DEBUG
run: |
set -x
-
pwd
-
ls -all
-
cd app/build/outputs/
ls -all
- tree
-
- # Optional: Sign APK (requires secrets)
- # - name: Sign APK
+ tree || ls -R
+
+ # Optional: Sign APKs (uncomment and configure secrets when ready)
+ # - name: Sign APKs
# uses: r0adkll/sign-android-release@v1
# with:
# releaseDirectory: app/build/outputs/apk/release
@@ -72,10 +72,10 @@ jobs:
# keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
# keyPassword: ${{ secrets.KEY_PASSWORD }}
- - name: Upload APK as artifact
+ - name: Upload APKs as artifacts
uses: actions/upload-artifact@v4
with:
- name: bitchat-release-apk-${{ github.ref_name }}
+ name: bitchat-android-release-${{ github.ref_name }}
path: app/build/outputs/apk/release/*.apk
retention-days: 30
if-no-files-found: error
@@ -85,16 +85,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Download APK artifact
+ - name: Download release artifacts
uses: actions/download-artifact@v4
with:
- name: bitchat-release-apk-${{ github.ref_name }}
- path: .
+ name: bitchat-android-release-${{ github.ref_name }}
+ path: release
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
- files: bitchat.apk
+ files: |
+ release/bitchat-android-arm64.apk
+ release/bitchat-android-x86_64.apk
+ release/bitchat-android-universal.apk
name: Release ${{ github.ref_name }}
+ body: |
+ **bitchat-android-arm64.apk** - ARM64 (most phones)
+ **bitchat-android-x86_64.apk** - x86_64 (Chromebooks, tablets)
+ **bitchat-android-universal.apk** - All architectures (fallback)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 151e1b4f..64ac199e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,7 +43,9 @@ dependency-reduced-pom.xml
# Other
*.log
.cxx/
-*build/
+# Gradle/Android build directories (but not tools/arti-build/)
+**/build/
+!tools/arti-build/
out/
gen/
*~
@@ -57,3 +59,7 @@ google-services.json
# Keystore files
*.jks
*.keystore
+
+# Arti build artifacts (cloned repo and Rust build cache)
+tools/arti-build/.arti-source/
+tools/arti-build/target/
diff --git a/README.md b/README.md
index 50f0cf54..c9b0091d 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,7 @@ The app requires the following permissions (automatically requested):
- **Bluetooth**: Core BLE functionality
- **Location**: Required for BLE scanning on Android
+- **Network**: Expand your mesh through public internet relays
- **Notifications**: Message alerts and background updates
### Hardware Requirements
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ca87d8dc..a789136e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -13,8 +13,8 @@ android {
applicationId = "com.bitchat.droid"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
- versionCode = 26
- versionName = "1.5.1"
+ versionCode = 30
+ versionName = "1.6.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -30,6 +30,12 @@ android {
}
buildTypes {
+ debug {
+ ndk {
+ // Include x86_64 for emulator support during development
+ abiFilters += listOf("arm64-v8a", "x86_64")
+ }
+ }
release {
isMinifyEnabled = true
isShrinkResources = true
@@ -39,6 +45,18 @@ android {
)
}
}
+
+ // APK splits for GitHub releases - creates arm64, x86_64, and universal APKs
+ // AAB for Play Store handles architecture distribution automatically
+ splits {
+ abi {
+ isEnable = true
+ reset()
+ include("arm64-v8a", "x86_64")
+ isUniversalApk = true // For F-Droid and fallback
+ }
+ }
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -73,12 +91,22 @@ dependencies {
// Lifecycle
implementation(libs.bundles.lifecycle)
+ implementation(libs.androidx.lifecycle.process)
// Navigation
implementation(libs.androidx.navigation.compose)
// Permissions
implementation(libs.accompanist.permissions)
+
+ // QR
+ implementation(libs.zxing.core)
+ implementation(libs.mlkit.barcode.scanning)
+
+ // CameraX
+ implementation(libs.androidx.camera.camera2)
+ implementation(libs.androidx.camera.lifecycle)
+ implementation(libs.androidx.camera.compose)
// Cryptography
implementation(libs.bundles.cryptography)
@@ -95,8 +123,11 @@ dependencies {
// WebSocket
implementation(libs.okhttp)
- // Arti (Tor in Rust) Android bridge - use published AAR with native libs
- implementation("info.guardianproject:arti-mobile-ex:1.2.3")
+ // Arti (Tor in Rust) Android bridge - custom build from latest source
+ // Built with rustls, 16KB page size support, and onio//un service client
+ // Native libraries are in src/tor/jniLibs/ (extracted from arti-custom.aar)
+ // Only included in tor flavor to reduce APK size for standard builds
+ // Note: AAR is kept in libs/ for reference, but libraries loaded from jniLibs/
// Google Play Services Location
implementation(libs.gms.location)
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index cd316d72..4bc0bb5c 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -243,17 +243,6 @@
column="35"/>
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -38,6 +52,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/nostr_relays.csv b/app/src/main/assets/nostr_relays.csv
index 834060a2..345b731b 100644
--- a/app/src/main/assets/nostr_relays.csv
+++ b/app/src/main/assets/nostr_relays.csv
@@ -1,277 +1,290 @@
Relay URL,Latitude,Longitude
-nostr-relay.amethyst.name,39.0438,-77.4874
-bcast.seutoba.com.br,43.6532,-79.3832
-premium.primal.net,40.7357,-74.1724
-strfry.openhoofd.nl,51.9229,4.40833
-relay.mostro.network,40.8302,-74.1299
-ribo.us.nostria.app,41.5868,-93.625
-relayone.geektank.ai,18.2148,-63.0574
-nostr.mikoshi.de,50.1109,8.68213
-relay.lifpay.me,1.35208,103.82
-shu02.shugur.net,21.4902,39.2246
-nostr.hifish.org,47.4043,8.57398
-orangepiller.org,60.1699,24.9384
-nostr.myshosholoza.co.za,52.3676,4.90414
-relay.nostrcheck.me,43.6532,-79.3832
-relay.nostrhub.tech,49.0291,8.35696
-nostr.mehdibekhtaoui.com,49.4939,-1.54813
-nostrcheck.me,40.7357,-74.1724
-relay.arx-ccn.com,50.4754,12.3683
-relay2.ngengine.org,43.6532,-79.3832
-nostr.21crypto.ch,47.4988,8.72369
-nostr.girino.org,43.6532,-79.3832
-relay.nostriches.club,43.6532,-79.3832
-relay.angor.io,48.1046,11.6002
-nostr.data.haus,50.4754,12.3683
-nostr.zoracle.org,45.6018,-121.185
-relay2.angor.io,48.1046,11.6002
-librerelay.aaroniumii.com,43.6532,-79.3832
-nostr.tadryanom.me,40.7357,-74.1724
-nostr-relay.cbrx.io,43.6532,-79.3832
-nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
-relay.aloftus.io,34.0881,-118.379
-relay.barine.co,43.6532,-79.3832
-nostr.red5d.dev,43.6532,-79.3832
-nos.lol,50.4754,12.3683
-ithurtswhenip.ee,51.223,6.78245
-strfry.bonsai.com,37.8715,-122.273
-relay01.lnfi.network,39.0997,-94.5786
-nostrcheck.tnsor.network,43.6532,-79.3832
-x.kojira.io,43.6532,-79.3832
-nostr.snowbla.de,60.1699,24.9384
-purplerelay.com,50.1109,8.68213
-wot.sovbit.host,64.1466,-21.9426
-shu04.shugur.net,25.2604,55.2989
-nr.yay.so,46.2126,6.1154
-nostr.smartflowsocial.com,43.6532,-79.3832
-shu05.shugur.net,48.8566,2.35222
-wot.dtonon.com,43.6532,-79.3832
-pyramid.fiatjaf.com,51.5072,-0.127586
-nostr.88mph.life,40.7357,-74.1724
-dev-nostr.bityacht.io,25.0797,121.234
-cyberspace.nostr1.com,40.7128,-74.006
-relay.ngengine.org,40.7357,-74.1724
-nostr.calitabby.net,39.9268,-75.0246
-relay.toastr.net,40.8054,-74.0241
-relay.cosmicbolt.net,37.3986,-121.964
-nostr.davidebtc.me,50.1109,8.68213
-espelho.girino.org,43.6532,-79.3832
-srtrelay.c-stellar.net,40.7357,-74.1724
-relay.nostriot.com,41.5695,-83.9786
-relay.bitcoinveneto.org,64.1466,-21.9426
-bitcoiner.social,39.1585,-94.5728
-purpura.cloud,40.7357,-74.1724
-nostr.blankfors.se,60.1699,24.9384
-relay.nostromo.social,49.4543,11.0746
-ribo.af.nostria.app,-26.2041,28.0473
-relay.olas.app,50.4754,12.3683
-nostr.carroarmato0.be,50.9928,3.26317
-nostr.oxtr.dev,50.4754,12.3683
-relay.upleb.uk,52.2297,21.0122
-nostr.stakey.net,52.3676,4.90414
-nostr2.girino.org,40.7357,-74.1724
-nostr.rikmeijer.nl,50.4754,12.3683
-relay5.bitransfer.org,40.7357,-74.1724
-relay.mattybs.lol,40.7357,-74.1724
-nostr.makibisskey.work,40.7357,-74.1724
-wot.basspistol.org,49.4521,11.0767
-relay.nostr.net,50.4754,12.3683
-a.nos.lol,50.4754,12.3683
-wot.dergigi.com,64.1476,-21.9392
-fanfares.nostr1.com,40.7057,-74.0136
-nostr.faultables.net,43.6532,-79.3832
-relay.dwadziesciajeden.pl,52.2297,21.0122
-adre.su,59.9311,30.3609
-wot.nostr.party,36.1627,-86.7816
-relay03.lnfi.network,39.0997,-94.5786
-relay.orangepill.ovh,49.1689,-0.358841
-gnostr.com,42.6978,23.3246
-relay.wellorder.net,45.5201,-122.99
-relay.21e6.cz,50.1682,14.0546
-relay.tagayasu.xyz,43.6715,-79.38
-relay.chatbett.de,28.0199,-82.5248
-wot.soundhsa.com,33.1384,-95.6011
-prl.plus,38.9072,-77.0369
-nostr.einundzwanzig.space,50.1109,8.68213
-nostream.breadslice.com,1.35208,103.82
-relay.chorus.community,50.1109,8.68213
-nostr.mom,50.4754,12.3683
-relay.lightning.pub,41.8959,-88.2169
-relay.hook.cafe,40.7357,-74.1724
-relay-testnet.k8s.layer3.news,37.3387,-121.885
-relay.fundstr.me,42.3601,-71.0589
-relay.stream.labs.h3.se,59.4016,17.9455
-relay.holzeis.me,43.6532,-79.3832
-articles.layer3.news,37.3387,-121.885
-nproxy.kristapsk.lv,60.1699,24.9384
-nostr.thebiglake.org,32.71,-96.6745
-relay.coinos.io,40.7357,-74.1724
-nostr.camalolo.com,24.1469,120.684
-theoutpost.life,64.1476,-21.9392
-relay.nostraddress.com,40.7357,-74.1724
-nostr.casa21.space,40.7357,-74.1724
-relay.agorist.space,52.3734,4.89406
-relay.nostr.wirednet.jp,34.706,135.493
-nostr.jerrynya.fun,31.2304,121.474
-relay.13room.space,43.6532,-79.3832
-offchain.pub,47.6743,-117.112
-relay.hasenpfeffr.com,39.0438,-77.4874
-nostr-verified.wellorder.net,45.5201,-122.99
-nostrelay.memory-art.xyz,43.6532,-79.3832
-freeben666.fr,43.1204,6.12857
-nostr.bilthon.dev,25.8128,-80.2377
-nostr.spicyz.io,40.7357,-74.1724
-bcast.girino.org,40.7357,-74.1724
-nostr.noones.com,50.1109,8.68213
-relay.electriclifestyle.com,26.2897,-80.1293
+alien.macneilmediagroup.com,43.6532,-79.3832
+v-relay.d02.vrtmrz.net,34.6937,135.502
+relay.thebluepulse.com,49.4521,11.0767
relay.guggero.org,47.3769,8.54169
-relay.siamdev.cc,13.8434,100.363
-relay.varke.eu,52.6921,6.19372
-nostr.now,36.55,139.733
-relay.origin.land,35.6673,139.751
-yabu.me,35.6092,139.73
-nostr-02.yakihonne.com,1.32123,103.695
-nostr.tac.lol,47.4748,-122.273
-r.bitcoinhold.net,40.7357,-74.1724
-nostr.rtvslawenia.com,49.4543,11.0746
-relay.sigit.io,50.4754,12.3683
-nostr-rs-relay-ishosta.phamthanh.me,40.7357,-74.1724
-relay.bitcoinartclock.com,50.4754,12.3683
-relay.snort.social,40.7357,-74.1724
-relay.mwaters.net,50.9871,2.12554
-nostr-03.dorafactory.org,1.35208,103.82
-relay.damus.io,40.7357,-74.1724
-black.nostrcity.club,41.8781,-87.6298
-noxir.kpherox.dev,34.8587,135.509
-relay.javi.space,43.4633,11.8796
-fenrir-s.notoshi.win,43.6532,-79.3832
-relay02.lnfi.network,39.0997,-94.5786
-mhp258zrpiiwn.clorecloud.net,40.7357,-74.1724
-relay.libernet.app,43.6532,-79.3832
-nostr.coincards.com,53.5501,-113.469
-ynostr.yael.at,60.1699,24.9384
-slick.mjex.me,39.048,-77.4817
-nostr.agentcampfire.com,50.8933,6.05805
-nostr-pub.wellorder.net,45.5201,-122.99
-relay.artiostr.ch,43.6532,-79.3832
-relay.davidebtc.me,50.1109,8.68213
-shu01.shugur.net,21.4902,39.2246
-relay.nostr.band,60.1699,24.9384
-relay.evanverma.com,40.8302,-74.1299
-nostr.simplex.icu,50.8198,-1.08798
-wot.nostr.net,43.6532,-79.3832
-relay.nostr.vet,52.6467,4.7395
-r.lostr.net,52.3676,4.90414
-relay.getsafebox.app,43.6532,-79.3832
-relay.moinsen.com,50.4754,12.3683
-nostr.luisschwab.net,43.6532,-79.3832
-strfry.elswa-dev.online,48.8566,2.35222
-alienos.libretechsystems.xyz,55.4724,9.87335
-nostr.overmind.lol,40.7357,-74.1724
-nos.xmark.cc,50.6924,3.20113
-relay.degmods.com,50.4754,12.3683
-relay.seq1.net,40.7128,-74.006
-relay.uid.ovh,43.6532,-79.3832
-santo.iguanatech.net,40.8302,-74.1299
-nostr.spaceshell.xyz,43.6532,-79.3832
-nostr.hekster.org,37.3986,-121.964
-wheat.happytavern.co,43.6532,-79.3832
-nostr-02.czas.top,53.471,9.88208
-satsage.xyz,37.3986,-121.964
-no.str.cr,9.92857,-84.0528
-relay.puresignal.news,40.7357,-74.1724
-nostr-relay.nextblockvending.com,47.2343,-119.853
-keys.nostr1.com,40.7057,-74.0136
-relayone.soundhsa.com,33.1384,-95.6011
-nostr-dev.wellorder.net,45.5201,-122.99
-alien.macneilmediagroup.com,40.7357,-74.1724
-nostr.kalf.org,52.3676,4.90414
-nostrelites.org,41.8781,-87.6298
-temp.iris.to,43.6532,-79.3832
-relay.utxo.farm,35.6916,139.768
-freelay.sovbit.host,64.1476,-21.9392
-relay.nosto.re,51.8933,4.42083
-inbox.azzamo.net,52.2633,21.0283
-nostr.ovia.to,43.6532,-79.3832
-nostr.lostr.space,40.7357,-74.1724
-nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832
-relay.nostar.org,40.7357,-74.1724
-relay.mccormick.cx,52.3563,4.95714
-nostr.0x7e.xyz,47.4988,8.72369
-relay.0xchat.com,1.35208,103.82
-relay.thebluepulse.com,40.7357,-74.1724
-relay.letsfo.com,51.098,17.0321
-nostrelay.circum.space,52.3676,4.90414
-nostr-relay.psfoundation.info,39.0438,-77.4874
-strfry.shock.network,41.8959,-88.2169
-nostr-2.21crypto.ch,47.4988,8.72369
-relay1.nostrchat.io,60.1699,24.9384
-nostr.plantroon.com,50.1013,8.62643
-relay.artx.market,43.652,-79.3633
-nostr.chaima.info,51.223,6.78245
-wot.nostr.place,30.2672,-97.7431
-nostr.rblb.it,43.4633,11.8796
-relay.magiccity.live,25.8128,-80.2377
-soloco.nl,40.7357,-74.1724
-dizzyspells.nostr1.com,40.7128,-74.006
-relay.zone667.com,60.1699,24.9384
-nostr-01.uid.ovh,43.6532,-79.3832
-zap.watch,45.5029,-73.5723
-relay.wolfcoil.com,35.6092,139.73
-relay.nostrdice.com,-33.8688,151.209
-relay.goodmorningbitcoin.com,43.6532,-79.3832
-relay.btcforplebs.com,40.7357,-74.1724
-nostr.sathoarder.com,48.5734,7.75211
-nostr.azzamo.net,52.2633,21.0283
-wot.utxo.one,40.7128,-74.006
-relay.bitcoindistrict.org,43.6532,-79.3832
relay.lumina.rocks,49.0291,8.35695
-relay.primal.net,43.6532,-79.3832
-nostr.notribe.net,40.8302,-74.1299
-relay.usefusion.ai,38.7134,-78.1591
-relay-dev.satlantis.io,40.8302,-74.1299
-nostr.vulpem.com,49.4543,11.0746
-orangesync.tech,50.1109,8.68213
-relay.fountain.fm,39.0997,-94.5786
-relay.cypherflow.ai,48.8566,2.35222
-nostr-relay.online,40.7357,-74.1724
-relay.internationalright-wing.org,-22.5022,-48.7114
-relay.nostr.place,32.7767,-96.797
-relay.npubhaus.com,40.7357,-74.1724
-relay.jeffg.fyi,43.6532,-79.3832
-nostr.night7.space,50.4754,12.3683
-nostr.4rs.nl,49.0291,8.35696
-nostr.liberty.fans,36.9104,-89.5875
-ribo.eu.nostria.app,52.3676,4.90414
-nostr.bitcoiner.social,39.1585,-94.5728
-relay.basspistol.org,46.2044,6.14316
-dev-relay.lnfi.network,39.0997,-94.5786
-nostr.openhoofd.nl,51.9229,4.40833
-nostr.huszonegy.world,47.4979,19.0402
-relay.2nix.de,60.1699,24.9384
+relay-freeharmonypeople.space,38.7223,-9.13934
+nostr-relay.online,43.6532,-79.3832
+shu05.shugur.net,48.8566,2.35222
+relay.evanverma.com,40.8302,-74.1299
+relay2.angor.io,48.1046,11.6002
+relay.arx-ccn.com,50.4754,12.3683
+relay.davidebtc.me,51.5072,-0.127586
+temp.iris.to,43.6532,-79.3832
+nostr.superfriends.online,43.6532,-79.3832
vitor.nostr1.com,40.7057,-74.0136
-nostr.zenon.network,43.5009,-70.4428
-relay.etch.social,41.2619,-95.8608
-relay.fr13nd5.com,52.5233,13.3426
-relay.agora.social,50.7383,15.0648
-relay.credenso.cafe,43.3601,-80.3127
-relay.illuminodes.com,47.6061,-122.333
-relay.freeplace.nl,52.3676,4.90414
-relay-rpi.edufeed.org,49.4543,11.0746
-relay.bullishbounty.com,43.6532,-79.3832
-relay04.lnfi.network,39.0997,-94.5786
-nostr-01.yakihonne.com,1.32123,103.695
-nostr-relay.xbytez.io,50.6924,3.20113
-wot.sebastix.social,51.8933,4.42083
-strfry.felixzieger.de,50.1013,8.62643
-nostr.n7ekb.net,47.4941,-122.294
-khatru.nostrver.se,51.8933,4.42083
-relayrs.notoshi.win,40.7357,-74.1724
-relay.wavlake.com,41.2619,-95.8608
+relay.moinsen.com,50.4754,12.3683
+ithurtswhenip.ee,51.223,6.78245
+nostr.tadryanom.me,43.6532,-79.3832
+relay.nostr.wirednet.jp,34.706,135.493
+relay.0xchat.com,1.35208,103.82
+nostrcheck.tnsor.network,43.6532,-79.3832
+relay.ngengine.org,43.6532,-79.3832
+relay.mitchelltribe.com,39.0438,-77.4874
+wotr.relatr.xyz,53.3498,-6.26031
+relay.chorus.community,50.1109,8.68213
+orly.ft.hn,50.4754,12.3683
+nostr.stakey.net,52.3676,4.90414
+relay.malxte.de,52.52,13.405
+nostrelay.circum.space,52.3676,4.90414
+nostrcheck.me,43.6532,-79.3832
wot.sudocarlos.com,51.5072,-0.127586
-nostr.sagaciousd.com,49.2827,-123.121
+relay.routstr.com,43.6532,-79.3832
+relay.siamdev.cc,13.8434,100.363
+wot.sovbit.host,64.1466,-21.9426
+strfry.bonsai.com,37.8715,-122.273
+freeben666.fr,43.7221,7.15296
+okn.czas.top,51.267,6.81738
+relay.nostar.org,43.6532,-79.3832
+relay.jeffg.fyi,43.6532,-79.3832
+nostr-02.uid.ovh,43.6532,-79.3832
+relay.fr13nd5.com,52.5233,13.3426
+srtrelay.c-stellar.net,43.6532,-79.3832
+relay.dwadziesciajeden.pl,52.2297,21.0122
+relay.sigit.io,50.4754,12.3683
+nostr.girino.org,43.6532,-79.3832
+bitsat.molonlabe.holdings,51.4012,-1.3147
+nostream.breadslice.com,1.35208,103.82
+nostr.rtvslawenia.com,49.4543,11.0746
+nostr.openhoofd.nl,51.9229,4.40833
+librerelay.aaroniumii.com,43.6532,-79.3832
+relay.etch.social,41.2619,-95.8608
+strfry.felixzieger.de,50.1013,8.62643
+relay.wavlake.com,41.2619,-95.8608
+relay.fundstr.me,42.3601,-71.0589
+slick.mjex.me,39.048,-77.4817
+relay.upleb.uk,51.9194,19.1451
+nos4smartnkind.tech,40.1872,44.5152
+discovery.eu.nostria.app,52.3676,4.90414
+nostr.tac.lol,47.4748,-122.273
+wot.nostr.net,43.6532,-79.3832
+nostr-dev.wellorder.net,45.5201,-122.99
+fanfares.nostr1.com,40.7128,-74.006
+relay.bitcoindistrict.org,43.6532,-79.3832
+nostr-relay.nextblockvending.com,47.2343,-119.853
+relay-rpi.edufeed.org,49.4521,11.0767
+relay.nsnip.io,60.1699,24.9384
+black.nostrcity.club,48.8575,2.35138
+nostr-02.czas.top,51.2277,6.77346
+schnorr.me,43.6532,-79.3832
+relay.fountain.fm,39.0997,-94.5786
+nostr-verified.wellorder.net,45.5201,-122.99
+relay.divine.video,43.6532,-79.3832
+relay.thibautduchene.fr,43.6532,-79.3832
+nostr.plantroon.com,50.1013,8.62643
+relay.hasenpfeffr.com,39.0438,-77.4874
+relay.chakany.systems,43.6532,-79.3832
+nostr-01.yakihonne.com,1.32123,103.695
+nostr.azzamo.net,52.2633,21.0283
+nostr.bond,50.1109,8.68213
+nostr.coincards.com,53.5501,-113.469
+relay.nostrhub.tech,49.0291,8.35696
+relay.cosmicbolt.net,37.3986,-121.964
+nostr.21crypto.ch,47.5356,8.73209
+relay-dev.satlantis.io,40.8302,-74.1299
+offchain.pub,47.6743,-117.112
+relay.orangepill.ovh,49.1689,-0.358841
+nostr-relay.gateway.in.th,15.2634,100.344
+nostr.now,36.55,139.733
+relay.usefusion.ai,38.7134,-78.1591
+nostr.lkjsxc.com,43.6532,-79.3832
+nproxy.kristapsk.lv,60.1699,24.9384
+relay.nostrverse.net,43.6532,-79.3832
+relay.zone667.com,60.1699,24.9384
+relay.origin.land,35.6673,139.751
+relay.damus.io,43.6532,-79.3832
+relay.bnos.space,1.35208,103.82
+relay.endfiat.money,43.6532,-79.3832
+relay.bullishbounty.com,43.6532,-79.3832
+wot.nostr.party,36.1627,-86.7816
+nostr.huszonegy.world,47.4979,19.0402
+theoutpost.life,64.1476,-21.9392
+nostr.noones.com,50.1109,8.68213
+relay.21e6.cz,50.7383,15.0648
+nostr-relay.xbytez.io,50.6924,3.20113
+wot.shaving.kiwi,43.6532,-79.3832
+relay.trustroots.org,43.6532,-79.3832
+wot.dtonon.com,43.6532,-79.3832
+nostr.camalolo.com,24.1469,120.684
+czas.xyz,48.8566,2.35222
+relayrs.notoshi.win,43.6532,-79.3832
+skeme.vanderwarker.family,40.8218,-74.45
+nostr.red5d.dev,43.6532,-79.3832
+relay.javi.space,43.4633,11.8796
+relay.nuts.cash,34.0362,-118.443
+nostr.commonshub.brussels,49.4543,11.0746
+relay.artx.market,43.652,-79.3633
+satsage.xyz,37.3986,-121.964
+relay.nostx.io,43.6532,-79.3832
+shu02.shugur.net,21.4902,39.2246
+strfry.ymir.cloud,34.0965,-117.585
+relay.nostr-check.me,43.6532,-79.3832
+relay04.lnfi.network,39.0997,-94.5786
+kotukonostr.onrender.com,37.7775,-122.397
+nostr.davidebtc.me,51.5072,-0.127586
+relay2.ngengine.org,43.6532,-79.3832
+ribo.us.nostria.app,41.5868,-93.625
+nostr.czas.top,50.1109,8.68213
+relay.nostrzh.org,43.6532,-79.3832
+relay.angor.io,48.1046,11.6002
+alienos.libretechsystems.xyz,55.4724,9.87335
+vault.iris.to,43.6532,-79.3832
+relay.mccormick.cx,52.3563,4.95714
+nostr.quali.chat,60.1699,24.9384
+wheat.happytavern.co,43.6532,-79.3832
+relay.olas.app,50.4754,12.3683
+nostr-relayrs.gateway.in.th,15.2634,100.344
+relay.snort.social,53.3498,-6.26031
+nostr-2.21crypto.ch,47.5356,8.73209
+pyramid.treegaze.com,43.6532,-79.3832
+relay.smies.me,33.7501,-84.3885
+wot.dergigi.com,64.1476,-21.9392
+nostr-relay.zimage.com,34.0549,-118.243
+nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
+nostr.jerrynya.fun,31.2304,121.474
+purplerelay.com,50.1109,8.68213
+relay.comcomponent.com,34.7062,135.493
+relay.unitypay.cash,41.4513,-81.7021
+nostr.mikoshi.de,50.1109,8.68213
+prl.plus,42.6978,23.3246
+shu04.shugur.net,25.2604,55.2989
+nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832
+nostr.zoracle.org,45.6018,-121.185
+nostriches.club,43.6532,-79.3832
+nostr.notribe.net,40.8302,-74.1299
+nostr.vulpem.com,49.4543,11.0746
+bucket.coracle.social,37.7775,-122.397
+wot.yesnostr.net,50.9871,2.12554
+wot.nostr.place,32.7767,-96.797
+nostr.mehdibekhtaoui.com,49.4939,-1.54813
+relay.getsafebox.app,43.6532,-79.3832
+nostr2.girino.org,43.6532,-79.3832
+nostr.blankfors.se,60.1699,24.9384
+premium.primal.net,43.6532,-79.3832
+nostr.rikmeijer.nl,50.4754,12.3683
+relay-fra.zombi.cloudrodion.com,48.8566,2.35222
+adre.su,59.9311,30.3609
+nostr.n7ekb.net,47.4941,-122.294
+nostr.hekster.org,37.3986,-121.964
+relay.jabato.space,52.52,13.405
+fenrir-s.notoshi.win,43.6532,-79.3832
+notemine.io,52.2026,20.9397
+r.bitcoinhold.net,43.6532,-79.3832
+nostr-pub.wellorder.net,45.5201,-122.99
+articles.layer3.news,37.3387,-121.885
+relays.diggoo.com,43.6532,-79.3832
+relayb.uid.ovh,43.6532,-79.3832
+nostr.thebiglake.org,32.71,-96.6745
+chat-relay.zap-work.com,43.6532,-79.3832
+nr.yay.so,46.2126,6.1154
+shu01.shugur.net,21.4902,39.2246
+khatru.nostrver.se,51.1792,5.89444
+relay.openfarmtools.org,60.1699,24.9384
+relay01.lnfi.network,39.0997,-94.5786
+nostrja-kari.heguro.com,43.6532,-79.3832
+nostr.snowbla.de,60.1699,24.9384
+relay.satlantis.io,32.8769,-80.0114
+relay.vrtmrz.net,43.6532,-79.3832
+purpura.cloud,43.6532,-79.3832
+orangepiller.org,60.1699,24.9384
+relay.coinos.io,43.6532,-79.3832
+relay.layer.systems,49.0291,8.35695
+relay.toastr.net,40.8054,-74.0241
+ynostr.yael.at,60.1699,24.9384
+ribo.eu.nostria.app,52.3676,4.90414
+relay.nostrcheck.me,43.6532,-79.3832
+relay.primal.net,43.6532,-79.3832
+relay-testnet.k8s.layer3.news,37.3387,-121.885
+relay.threenine.services,51.5524,-0.29686
+relay.nostr.net,43.6532,-79.3832
+nostr.middling.mydns.jp,35.8099,140.12
+soloco.nl,43.6532,-79.3832
+nostr.4rs.nl,49.0291,8.35696
+no.str.cr,9.92857,-84.0528
+espelho.girino.org,43.6532,-79.3832
+nostr.hifish.org,47.4043,8.57398
+wot.sebastix.social,51.1792,5.89444
+relay03.lnfi.network,39.0997,-94.5786
+relay.nosto.re,51.1792,5.89444
+relay.nostrdice.com,-33.8688,151.209
+nostr.agentcampfire.com,52.3676,4.90414
+nostr.night7.space,50.4754,12.3683
+nostr.nodesmap.com,59.3327,18.0656
+relayone.soundhsa.com,33.1384,-95.6011
+relay.illuminodes.com,47.6061,-122.333
+nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832
+nostr.robosats.org,64.1476,-21.9392
+relay.seq1.net,43.6532,-79.3832
+strfry.shock.network,39.0438,-77.4874
+nostr.ovia.to,43.6532,-79.3832
+relay.holzeis.me,43.6532,-79.3832
+bcast.seutoba.com.br,43.6532,-79.3832
+relay.libernet.app,43.6532,-79.3832
+nostr-01.uid.ovh,43.6532,-79.3832
+nostr.luisschwab.net,43.6532,-79.3832
+strfry.elswa-dev.online,50.1109,8.68213
+relay.nostriot.com,41.5695,-83.9786
+nostr.oxtr.dev,50.4754,12.3683
+nostr.mom,50.4754,12.3683
+nostr-03.dorafactory.org,1.35208,103.82
+relay.camelus.app,45.5201,-122.99
+strfry.openhoofd.nl,51.9229,4.40833
+nos.lol,50.4754,12.3683
+nostr.bitcoiner.social,39.1585,-94.5728
relay.islandbitcoin.com,12.8498,77.6545
-trizone.dev,1.35208,103.82
-schnorr.me,40.7357,-74.1724
-relay.vrtmrz.net,40.7357,-74.1724
+relay.tagayasu.xyz,43.6715,-79.38
+dev-nostr.bityacht.io,25.0797,121.234
+relay.nostrhub.fr,48.1045,11.6004
+relay.satmaxt.xyz,43.6532,-79.3832
+yabu.me,35.6092,139.73
+relay.samt.st,40.8302,-74.1299
+relay02.lnfi.network,39.0997,-94.5786
+nostr.88mph.life,51.5072,-0.127586
+dev-relay.lnfi.network,39.0997,-94.5786
+relay.goodmorningbitcoin.com,43.6532,-79.3832
+nostrelay.memory-art.xyz,43.6532,-79.3832
+relay.degmods.com,50.4754,12.3683
+nostr-02.yakihonne.com,1.32123,103.695
+nostr.spicyz.io,43.6532,-79.3832
+relay.minibolt.info,43.6532,-79.3832
+bcast.girino.org,43.6532,-79.3832
+nostr.bilthon.dev,25.8128,-80.2377
+relay.bitcoinartclock.com,50.4754,12.3683
+cyberspace.nostr1.com,40.7128,-74.006
+nostr.data.haus,50.4754,12.3683
+relay.agora.social,50.7383,15.0648
+relay.wavefunc.live,34.0362,-118.443
+nostr.casa21.space,43.6532,-79.3832
+relay.mostro.network,40.8302,-74.1299
relay.ditto.pub,43.6532,-79.3832
+nostr-relay.amethyst.name,39.0438,-77.4874
+relay.credenso.cafe,43.3601,-80.3127
+nostr.calitabby.net,39.9268,-75.0246
+relay.magiccity.live,25.8128,-80.2377
+nostr.chaima.info,51.223,6.78245
+wot.brightbolt.net,47.6735,-116.781
+nostr.sathoarder.com,48.5734,7.75211
+inbox.azzamo.net,52.2633,21.0283
+nostr.na.social,43.6532,-79.3832
+nostr.myshosholoza.co.za,52.3676,4.90414
+nostr.spaceshell.xyz,43.6532,-79.3832
+relay.wellorder.net,45.5201,-122.99
+nostr.0x7e.xyz,47.4988,8.72369
+nostr-relay.cbrx.io,43.6532,-79.3832
+relay.btcforplebs.com,43.6532,-79.3832
+bitcoiner.social,39.1585,-94.5728
+relaynostr.breadslice.com,43.6532,-79.3832
+nostr.overmind.lol,43.6532,-79.3832
+santo.iguanatech.net,40.8302,-74.1299
+relay.lightning.pub,39.0438,-77.4874
+nostr.rblb.it,43.7094,10.6582
+relay.bitcoinveneto.org,64.1466,-21.9426
+relay.wolfcoil.com,35.6092,139.73
+nostr-relay.corb.net,38.8353,-104.822
+relay.nostr.place,32.7767,-96.797
+wot.soundhsa.com,33.1384,-95.6011
+relay.npubhaus.com,43.6532,-79.3832
+nostr-relay.psfoundation.info,39.0438,-77.4874
+x.kojira.io,43.6532,-79.3832
+relay.cypherflow.ai,48.8566,2.35222
diff --git a/app/src/main/java/com/bitchat/android/BitchatApplication.kt b/app/src/main/java/com/bitchat/android/BitchatApplication.kt
index df9cd6e5..b120b216 100644
--- a/app/src/main/java/com/bitchat/android/BitchatApplication.kt
+++ b/app/src/main/java/com/bitchat/android/BitchatApplication.kt
@@ -3,18 +3,21 @@ package com.bitchat.android
import android.app.Application
import com.bitchat.android.nostr.RelayDirectory
import com.bitchat.android.ui.theme.ThemePreferenceManager
-import com.bitchat.android.net.TorManager
+import com.bitchat.android.net.ArtiTorManager
/**
* Main application class for bitchat Android
*/
class BitchatApplication : Application() {
-
+
override fun onCreate() {
super.onCreate()
-
+
// Initialize Tor first so any early network goes over Tor
- try { TorManager.init(this) } catch (_: Exception) { }
+ try {
+ val torProvider = ArtiTorManager.getInstance()
+ torProvider.init(this)
+ } catch (_: Exception){}
// Initialize relay directory (loads assets/nostr_relays.csv)
RelayDirectory.initialize(this)
@@ -38,6 +41,12 @@ class BitchatApplication : Application() {
// Initialize debug preference manager (persists debug toggles)
try { com.bitchat.android.ui.debug.DebugPreferenceManager.init(this) } catch (_: Exception) { }
+ // Initialize mesh service preferences
+ try { com.bitchat.android.service.MeshServicePreferences.init(this) } catch (_: Exception) { }
+
+ // Proactively start the foreground service to keep mesh alive
+ try { com.bitchat.android.service.MeshForegroundService.start(this) } catch (_: Exception) { }
+
// TorManager already initialized above
}
}
diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt
index 28d67280..f287784d 100644
--- a/app/src/main/java/com/bitchat/android/MainActivity.kt
+++ b/app/src/main/java/com/bitchat/android/MainActivity.kt
@@ -26,6 +26,7 @@ import com.bitchat.android.onboarding.BatteryOptimizationManager
import com.bitchat.android.onboarding.BatteryOptimizationPreferenceManager
import com.bitchat.android.onboarding.BatteryOptimizationScreen
import com.bitchat.android.onboarding.BatteryOptimizationStatus
+import com.bitchat.android.onboarding.BackgroundLocationPermissionScreen
import com.bitchat.android.onboarding.InitializationErrorScreen
import com.bitchat.android.onboarding.InitializingScreen
import com.bitchat.android.onboarding.LocationCheckScreen
@@ -40,6 +41,7 @@ import com.bitchat.android.ui.ChatViewModel
import com.bitchat.android.ui.OrientationAwareActivity
import com.bitchat.android.ui.theme.BitchatTheme
import com.bitchat.android.nostr.PoWPreferenceManager
+import com.bitchat.android.services.VerificationService
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -51,7 +53,7 @@ class MainActivity : OrientationAwareActivity() {
private lateinit var locationStatusManager: LocationStatusManager
private lateinit var batteryOptimizationManager: BatteryOptimizationManager
- // Core mesh service - managed at app level
+ // Core mesh service - provided by the foreground service holder
private lateinit var meshService: BluetoothMeshService
private val mainViewModel: MainViewModel by viewModels()
private val chatViewModel: ChatViewModel by viewModels {
@@ -63,16 +65,55 @@ class MainActivity : OrientationAwareActivity() {
}
}
+ private val forceFinishReceiver = object : android.content.BroadcastReceiver() {
+ override fun onReceive(context: android.content.Context, intent: android.content.Intent) {
+ if (intent.action == com.bitchat.android.util.AppConstants.UI.ACTION_FORCE_FINISH) {
+ android.util.Log.i("MainActivity", "Received force finish broadcast, closing UI")
+ finishAffinity()
+ }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ // Register receiver for force finish signal from shutdown coordinator
+ val filter = android.content.IntentFilter(com.bitchat.android.util.AppConstants.UI.ACTION_FORCE_FINISH)
+ if (android.os.Build.VERSION.SDK_INT >= 33) {
+ registerReceiver(
+ forceFinishReceiver,
+ filter,
+ com.bitchat.android.util.AppConstants.UI.PERMISSION_FORCE_FINISH,
+ null,
+ android.content.Context.RECEIVER_NOT_EXPORTED
+ )
+ } else {
+ @Suppress("DEPRECATION")
+ registerReceiver(
+ forceFinishReceiver,
+ filter,
+ com.bitchat.android.util.AppConstants.UI.PERMISSION_FORCE_FINISH,
+ null
+ )
+ }
+
+ // Check if this is a quit request from the notification
+ if (intent.getBooleanExtra("ACTION_QUIT_APP", false)) {
+ android.util.Log.d("MainActivity", "Quit request received in onCreate, finishing activity")
+ finish()
+ return
+ }
+
+ com.bitchat.android.service.AppShutdownCoordinator.cancelPendingShutdown()
+
// Enable edge-to-edge display for modern Android look
enableEdgeToEdge()
// Initialize permission management
permissionManager = PermissionManager(this)
- // Initialize core mesh service first
- meshService = BluetoothMeshService(this)
+ // Ensure foreground service is running and get mesh instance from holder
+ try { com.bitchat.android.service.MeshForegroundService.start(applicationContext) } catch (_: Exception) { }
+ meshService = com.bitchat.android.service.MeshServiceHolder.getOrCreate(applicationContext)
bluetoothStatusManager = BluetoothStatusManager(
activity = this,
context = this,
@@ -95,6 +136,9 @@ class MainActivity : OrientationAwareActivity() {
activity = this,
permissionManager = permissionManager,
onOnboardingComplete = ::handleOnboardingComplete,
+ onBackgroundLocationRequired = {
+ mainViewModel.updateOnboardingState(OnboardingState.BACKGROUND_LOCATION_EXPLANATION)
+ },
onOnboardingFailed = ::handleOnboardingFailed
)
@@ -227,6 +271,21 @@ class MainActivity : OrientationAwareActivity() {
)
}
+ OnboardingState.BACKGROUND_LOCATION_EXPLANATION -> {
+ BackgroundLocationPermissionScreen(
+ modifier = modifier,
+ onContinue = {
+ onboardingCoordinator.requestBackgroundLocation()
+ },
+ onRetry = {
+ onboardingCoordinator.checkBackgroundLocationAndProceed()
+ },
+ onSkip = {
+ onboardingCoordinator.skipBackgroundLocation()
+ }
+ )
+ }
+
OnboardingState.CHECKING, OnboardingState.INITIALIZING, OnboardingState.COMPLETE -> {
// Set up back navigation handling for the chat screen
val backCallback = object : OnBackPressedCallback(true) {
@@ -340,10 +399,17 @@ class MainActivity : OrientationAwareActivity() {
if (permissionManager.isFirstTimeLaunch()) {
Log.d("MainActivity", "First time launch, showing permission explanation")
mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_EXPLANATION)
- } else if (permissionManager.areAllPermissionsGranted()) {
- Log.d("MainActivity", "Existing user with permissions, initializing app")
- mainViewModel.updateOnboardingState(OnboardingState.INITIALIZING)
- initializeApp()
+ } else if (permissionManager.areRequiredPermissionsGranted()) {
+ Log.d("MainActivity", "Existing user with required permissions")
+ if (permissionManager.needsBackgroundLocationPermission() &&
+ !permissionManager.isBackgroundLocationGranted() &&
+ !com.bitchat.android.onboarding.BackgroundLocationPreferenceManager.isSkipped(this@MainActivity)
+ ) {
+ mainViewModel.updateOnboardingState(OnboardingState.BACKGROUND_LOCATION_EXPLANATION)
+ } else {
+ mainViewModel.updateOnboardingState(OnboardingState.INITIALIZING)
+ initializeApp()
+ }
} else {
Log.d("MainActivity", "Existing user missing permissions, showing explanation")
mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_EXPLANATION)
@@ -616,6 +682,7 @@ class MainActivity : OrientationAwareActivity() {
// Handle any notification intent
handleNotificationIntent(intent)
+ handleVerificationIntent(intent)
// Small delay to ensure mesh service is fully initialized
delay(500)
@@ -630,9 +697,21 @@ class MainActivity : OrientationAwareActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
+ setIntent(intent)
+
+ // Check if this is a quit request from the notification
+ if (intent.getBooleanExtra("ACTION_QUIT_APP", false)) {
+ android.util.Log.d("MainActivity", "Quit request received, finishing activity")
+ finish()
+ return
+ }
+
+ com.bitchat.android.service.AppShutdownCoordinator.cancelPendingShutdown()
+
// Handle notification intents when app is already running
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
handleNotificationIntent(intent)
+ handleVerificationIntent(intent)
}
}
@@ -640,9 +719,8 @@ class MainActivity : OrientationAwareActivity() {
super.onResume()
// Check Bluetooth and Location status on resume and handle accordingly
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
- // Set app foreground state
- meshService.connectionManager.setAppBackgroundState(false)
- chatViewModel.setAppBackgroundState(false)
+ // Reattach mesh delegate to new ChatViewModel instance after Activity recreation
+ try { meshService.delegate = chatViewModel } catch (_: Exception) { }
// Check if Bluetooth was disabled while app was backgrounded
val currentBluetoothStatus = bluetoothStatusManager.checkBluetoothStatus()
@@ -665,13 +743,12 @@ class MainActivity : OrientationAwareActivity() {
}
}
- override fun onPause() {
+ override fun onPause() {
super.onPause()
// Only set background state if app is fully initialized
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
- // Set app background state
- meshService.connectionManager.setAppBackgroundState(true)
- chatViewModel.setAppBackgroundState(true)
+ // Detach UI delegate so the foreground service can own DM notifications while UI is closed
+ try { meshService.delegate = null } catch (_: Exception) { }
}
}
@@ -734,10 +811,23 @@ class MainActivity : OrientationAwareActivity() {
}
}
+ private fun handleVerificationIntent(intent: Intent) {
+ val uri = intent.data ?: return
+ if (uri.scheme != "bitchat" || uri.host != "verify") return
+
+ chatViewModel.showVerificationSheet()
+ val qr = VerificationService.verifyScannedQR(uri.toString())
+ if (qr != null) {
+ chatViewModel.beginQRVerification(qr)
+ }
+ }
+
override fun onDestroy() {
super.onDestroy()
+ try { unregisterReceiver(forceFinishReceiver) } catch (_: Exception) { }
+
// Cleanup location status manager
try {
locationStatusManager.cleanup()
@@ -746,14 +836,6 @@ class MainActivity : OrientationAwareActivity() {
Log.w("MainActivity", "Error cleaning up location status manager: ${e.message}")
}
- // Stop mesh services if app was fully initialized
- if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
- try {
- meshService.stopServices()
- Log.d("MainActivity", "Mesh services stopped successfully")
- } catch (e: Exception) {
- Log.w("MainActivity", "Error stopping mesh services in onDestroy: ${e.message}")
- }
- }
+ // Do not stop mesh here; ForegroundService owns lifecycle for background reliability
}
}
diff --git a/app/src/main/java/com/bitchat/android/core/ui/component/button/CloseButton.kt b/app/src/main/java/com/bitchat/android/core/ui/component/button/CloseButton.kt
new file mode 100644
index 00000000..96f408bc
--- /dev/null
+++ b/app/src/main/java/com/bitchat/android/core/ui/component/button/CloseButton.kt
@@ -0,0 +1,34 @@
+package com.bitchat.android.core.ui.component.button
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun CloseButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier.Companion
+) {
+ IconButton(
+ onClick = onClick,
+ modifier = modifier
+ .size(32.dp),
+ colors = IconButtonDefaults.iconButtonColors(
+ contentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
+ containerColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f)
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Default.Close,
+ contentDescription = "Close",
+ modifier = Modifier.Companion.size(18.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt b/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt
index 449d705f..c615d62b 100644
--- a/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt
+++ b/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt
@@ -19,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap
* This is the main interface for all encryption/decryption operations in bitchat.
* It now uses the Noise protocol for secure transport encryption with proper session management.
*/
-class EncryptionService(private val context: Context) {
+open class EncryptionService(private val context: Context) {
companion object {
private const val TAG = "EncryptionService"
@@ -27,14 +27,14 @@ class EncryptionService(private val context: Context) {
}
// Core Noise encryption service
- private val noiseService: NoiseEncryptionService = NoiseEncryptionService(context)
+ private val noiseService: NoiseEncryptionService by lazy { NoiseEncryptionService(context) }
// Session tracking for established connections
private val establishedSessions = ConcurrentHashMap() // peerID -> fingerprint
// Ed25519 signing keys (separate from Noise static keys)
- private val ed25519PrivateKey: Ed25519PrivateKeyParameters
- private val ed25519PublicKey: Ed25519PublicKeyParameters
+ private lateinit var ed25519PrivateKey: Ed25519PrivateKeyParameters
+ private lateinit var ed25519PublicKey: Ed25519PublicKeyParameters
// Callbacks for UI state updates
var onSessionEstablished: ((String) -> Unit)? = null // peerID
@@ -42,6 +42,13 @@ class EncryptionService(private val context: Context) {
var onHandshakeRequired: ((String) -> Unit)? = null // peerID
init {
+ initialize()
+ }
+
+ /**
+ * Initialization logic moved to method to allow overriding in tests
+ */
+ protected open fun initialize() {
// Initialize or load Ed25519 signing keys
val keyPair = loadOrCreateEd25519KeyPair()
ed25519PrivateKey = keyPair.private as Ed25519PrivateKeyParameters
@@ -356,7 +363,7 @@ class EncryptionService(private val context: Context) {
/**
* Verify Ed25519 signature against data using a public key
*/
- fun verifyEd25519Signature(signature: ByteArray, data: ByteArray, publicKeyBytes: ByteArray): Boolean {
+ open fun verifyEd25519Signature(signature: ByteArray, data: ByteArray, publicKeyBytes: ByteArray): Boolean {
return try {
val publicKey = Ed25519PublicKeyParameters(publicKeyBytes, 0)
val verifier = Ed25519Signer()
diff --git a/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt b/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt
index b498dd83..d4ccb9bf 100644
--- a/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt
+++ b/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt
@@ -5,13 +5,14 @@ import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import android.util.Log
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import java.util.Locale
/**
@@ -46,11 +47,11 @@ class GeohashBookmarksStore private constructor(private val context: Context) {
private val membership = mutableSetOf()
- private val _bookmarks = MutableLiveData>(emptyList())
- val bookmarks: LiveData> = _bookmarks
+ private val _bookmarks = MutableStateFlow>(emptyList())
+ val bookmarks: StateFlow> = _bookmarks.asStateFlow()
- private val _bookmarkNames = MutableLiveData