Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
aeonBTC authored Dec 19, 2024
1 parent 026fc6e commit 7280c4b
Show file tree
Hide file tree
Showing 11 changed files with 746 additions and 633 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ android {
applicationId = "com.example.mempal"
minSdk = 24
targetSdk = 34
versionCode = 8
versionName = "1.4.0"
versionCode = 9
versionName = "1.4.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled = true
}
Expand Down
917 changes: 498 additions & 419 deletions app/src/main/java/com/example/mempal/MainActivity.kt

Large diffs are not rendered by default.

39 changes: 31 additions & 8 deletions app/src/main/java/com/example/mempal/api/NetworkClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit

object NetworkClient {
private const val TIMEOUT_SECONDS = 30L
private const val TIMEOUT_SECONDS = 25L
private const val TEST_TIMEOUT_SECONDS = 15L
private const val ONION_TEST_TIMEOUT_SECONDS = 45L
private var retrofit: Retrofit? = null
private var contextRef: WeakReference<Context>? = null
private val _isInitialized = MutableStateFlow(false)
Expand All @@ -35,16 +37,16 @@ object NetworkClient {
super.onAvailable(network)
coroutineScope?.launch {
_isNetworkAvailable.value = true
if (_isInitialized.value) {
// Reinitialize the client when network becomes available
setupRetrofit(TorManager.getInstance().torStatus.value == TorStatus.CONNECTED)
}
// Always try to reinitialize when network becomes available
setupRetrofit(TorManager.getInstance().torStatus.value == TorStatus.CONNECTED)
_isInitialized.value = true
}
}

override fun onLost(network: Network) {
super.onLost(network)
_isNetworkAvailable.value = false
_isInitialized.value = false
}
}

Expand All @@ -71,7 +73,20 @@ object NetworkClient {
// Check initial network state
_isNetworkAvailable.value = isNetworkCurrentlyAvailable()

// Check if current API URL is an onion address and manage Tor accordingly
val settingsRepository = SettingsRepository.getInstance(context)
val currentApiUrl = settingsRepository.getApiUrl()
val torManager = TorManager.getInstance()

if (currentApiUrl.contains(".onion")) {
if (!torManager.isTorEnabled()) {
println("Onion address detected, enabling Tor")
torManager.startTor(context)
}
} else if (torManager.isTorEnabled()) {
println("Non-onion address detected, disabling Tor")
torManager.stopTor(context)
}

coroutineScope?.launch {
torManager.torStatus.collect { status ->
Expand All @@ -82,7 +97,11 @@ object NetworkClient {
setupRetrofit(status == TorStatus.CONNECTED)
_isInitialized.value = true
println("NetworkClient initialization complete")
} else {
_isInitialized.value = false
}
} else {
_isInitialized.value = false
}
}
}
Expand Down Expand Up @@ -146,9 +165,9 @@ object NetworkClient {
fun createTestClient(baseUrl: String, useTor: Boolean = false): MempoolApi {
val clientBuilder = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.connectTimeout(if (useTor && baseUrl.contains(".onion")) ONION_TEST_TIMEOUT_SECONDS else TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(if (useTor && baseUrl.contains(".onion")) ONION_TEST_TIMEOUT_SECONDS else TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(if (useTor && baseUrl.contains(".onion")) ONION_TEST_TIMEOUT_SECONDS else TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)

if (useTor && baseUrl.contains(".onion")) {
clientBuilder.proxy(java.net.Proxy(
Expand All @@ -167,4 +186,8 @@ object NetworkClient {

return testRetrofit.create(MempoolApi::class.java)
}

fun isUsingOnion(): Boolean {
return mempoolApi.toString().contains(".onion")
}
}
12 changes: 11 additions & 1 deletion app/src/main/java/com/example/mempal/api/WidgetNetworkClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

object WidgetNetworkClient {
private const val TIMEOUT_SECONDS = 30L
private const val TIMEOUT_SECONDS = 10L
private const val DEFAULT_API_URL = "https://mempool.space"

private val loggingInterceptor = HttpLoggingInterceptor().apply {
Expand All @@ -36,6 +36,16 @@ object WidgetNetworkClient {
.readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor { chain ->
var attempt = 0
var response = chain.proceed(chain.request())
while (!response.isSuccessful && attempt < 2) {
attempt++
response.close()
response = chain.proceed(chain.request())
}
response
}

val gson = GsonBuilder()
.setLenient()
Expand Down
120 changes: 60 additions & 60 deletions app/src/main/java/com/example/mempal/cache/DashboardCache.kt
Original file line number Diff line number Diff line change
@@ -1,61 +1,61 @@
package com.example.mempal.cache

import com.example.mempal.api.FeeRates
import com.example.mempal.api.MempoolInfo

// Singleton object to store dashboard data in memory
object DashboardCache {
private var blockHeight: Int? = null
private var blockTimestamp: Long? = null
private var mempoolInfo: MempoolInfo? = null
private var feeRates: FeeRates? = null
private var lastUpdateTime: Long? = null

// Save all dashboard data at once
fun saveState(
blockHeight: Int?,
blockTimestamp: Long?,
mempoolInfo: MempoolInfo?,
feeRates: FeeRates?
) {
this.blockHeight = blockHeight
this.blockTimestamp = blockTimestamp
this.mempoolInfo = mempoolInfo
this.feeRates = feeRates
this.lastUpdateTime = System.currentTimeMillis()
}

// Get cached state
fun getCachedState(): DashboardState {
return DashboardState(
blockHeight = blockHeight,
blockTimestamp = blockTimestamp,
mempoolInfo = mempoolInfo,
feeRates = feeRates,
lastUpdateTime = lastUpdateTime
)
}

// Check if we have any cached data
fun hasCachedData(): Boolean {
return blockHeight != null || mempoolInfo != null || feeRates != null
}

// Clear cache (useful when app is closing)
fun clearCache() {
blockHeight = null
blockTimestamp = null
mempoolInfo = null
feeRates = null
lastUpdateTime = null
}
}

// Data class to hold all dashboard state
data class DashboardState(
val blockHeight: Int?,
val blockTimestamp: Long?,
val mempoolInfo: MempoolInfo?,
val feeRates: FeeRates?,
val lastUpdateTime: Long?
package com.example.mempal.cache

import com.example.mempal.api.FeeRates
import com.example.mempal.api.MempoolInfo

// Singleton object to store dashboard data in memory
object DashboardCache {
private var blockHeight: Int? = null
private var blockTimestamp: Long? = null
private var mempoolInfo: MempoolInfo? = null
private var feeRates: FeeRates? = null
private var lastUpdateTime: Long? = null

// Save all dashboard data at once
fun saveState(
blockHeight: Int?,
blockTimestamp: Long?,
mempoolInfo: MempoolInfo?,
feeRates: FeeRates?
) {
this.blockHeight = blockHeight
this.blockTimestamp = blockTimestamp
this.mempoolInfo = mempoolInfo
this.feeRates = feeRates
this.lastUpdateTime = System.currentTimeMillis()
}

// Get cached state
fun getCachedState(): DashboardState {
return DashboardState(
blockHeight = blockHeight,
blockTimestamp = blockTimestamp,
mempoolInfo = mempoolInfo,
feeRates = feeRates,
lastUpdateTime = lastUpdateTime
)
}

// Check if we have any cached data
fun hasCachedData(): Boolean {
return blockHeight != null || mempoolInfo != null || feeRates != null
}

// Clear cache (useful when app is closing)
fun clearCache() {
blockHeight = null
blockTimestamp = null
mempoolInfo = null
feeRates = null
lastUpdateTime = null
}
}

// Data class to hold all dashboard state
data class DashboardState(
val blockHeight: Int?,
val blockTimestamp: Long?,
val mempoolInfo: MempoolInfo?,
val feeRates: FeeRates?,
val lastUpdateTime: Long?
)
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class SettingsRepository private constructor(context: Context) {
private const val KEY_TX_CONFIRMATION_FREQUENCY = "tx_confirmation_frequency"
private const val KEY_TRANSACTION_ID = "transaction_id"

private const val KEY_VISIBLE_CARDS = "visible_cards"
private val DEFAULT_VISIBLE_CARDS = setOf("Block Height", "Mempool Size", "Fee Rates", "Fee Distribution")

fun getInstance(context: Context): SettingsRepository {
val currentInstance = instance?.get()
if (currentInstance != null) {
Expand Down Expand Up @@ -177,4 +180,12 @@ class SettingsRepository private constructor(context: Context) {
currentServers.remove(url)
prefs.edit().putStringSet(KEY_SAVED_SERVERS, currentServers).apply()
}

fun getVisibleCards(): Set<String> {
return prefs.getStringSet(KEY_VISIBLE_CARDS, DEFAULT_VISIBLE_CARDS) ?: DEFAULT_VISIBLE_CARDS
}

fun saveVisibleCards(visibleCards: Set<String>) {
prefs.edit().putStringSet(KEY_VISIBLE_CARDS, visibleCards).apply()
}
}
29 changes: 19 additions & 10 deletions app/src/main/java/com/example/mempal/service/NotificationService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ class NotificationService : Service() {
val delayMultiplier = if (timeUnit == "seconds") 1000L else 60000L
android.util.Log.d(TAG, "Time unit: $timeUnit, Delay multiplier: $delayMultiplier")

if (settings.newBlockNotificationEnabled) {
// New Block Notifications - requires frequency > 0
if (settings.newBlockNotificationEnabled && settings.newBlockCheckFrequency > 0) {
monitoringJobs["newBlock"] = launch {
// Initial delay before first check
delay(settings.newBlockCheckFrequency * delayMultiplier)
while (isActive) {
val now = System.currentTimeMillis()
Expand All @@ -102,9 +102,11 @@ class NotificationService : Service() {
}
}

if (settings.specificBlockNotificationEnabled) {
// Specific Block Notifications - requires target height and frequency > 0
if (settings.specificBlockNotificationEnabled &&
settings.specificBlockCheckFrequency > 0 &&
settings.targetBlockHeight != null) {
monitoringJobs["specificBlock"] = launch {
// Initial delay before first check
delay(settings.specificBlockCheckFrequency * delayMultiplier)
while (isActive) {
val now = System.currentTimeMillis()
Expand All @@ -121,9 +123,11 @@ class NotificationService : Service() {
}
}

if (settings.mempoolSizeNotificationsEnabled) {
// Mempool Size Notifications - requires threshold > 0 and frequency > 0
if (settings.mempoolSizeNotificationsEnabled &&
settings.mempoolCheckFrequency > 0 &&
settings.mempoolSizeThreshold > 0) {
monitoringJobs["mempoolSize"] = launch {
// Initial delay before first check
delay(settings.mempoolCheckFrequency * delayMultiplier)
while (isActive) {
val now = System.currentTimeMillis()
Expand All @@ -140,9 +144,11 @@ class NotificationService : Service() {
}
}

if (settings.feeRatesNotificationsEnabled) {
// Fee Rate Notifications - requires threshold > 0 and frequency > 0
if (settings.feeRatesNotificationsEnabled &&
settings.feeRatesCheckFrequency > 0 &&
settings.feeRateThreshold > 0) {
monitoringJobs["feeRates"] = launch {
// Initial delay before first check
delay(settings.feeRatesCheckFrequency * delayMultiplier)
while (isActive) {
val now = System.currentTimeMillis()
Expand All @@ -159,9 +165,12 @@ class NotificationService : Service() {
}
}

if (settings.txConfirmationEnabled && settings.transactionId.isNotEmpty()) {
// Transaction Confirmation Notifications - requires valid txid and frequency > 0
if (settings.txConfirmationEnabled &&
settings.txConfirmationFrequency > 0 &&
settings.transactionId.isNotEmpty() &&
settings.transactionId.length >= 64) { // Valid txid is 64 chars
monitoringJobs["txConfirmation"] = launch {
// Initial delay before first check
delay(settings.txConfirmationFrequency * delayMultiplier)
while (isActive) {
val now = System.currentTimeMillis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class TorForegroundService : Service() {
return NotificationCompat.Builder(this, NotificationService.CHANNEL_ID)
.setContentTitle("Mempal")
.setContentText("Monitoring Bitcoin Network")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setSmallIcon(R.drawable.ic_cube)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
.build()
Expand Down
Loading

0 comments on commit 7280c4b

Please sign in to comment.