Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a746d3d
Merge pull request #53 from gruntsoftware/release/v4.4.1
kcw-grunt Mar 30, 2025
ba8ea45
Revert "Updated the APIManager"
kcw-grunt Mar 31, 2025
638ce3a
Reverted the Kotlin APIManager.
kcw-grunt Mar 31, 2025
5344a96
Add version and code to welcome screen
kcw-grunt Mar 31, 2025
ebc3bb9
reset padding
kcw-grunt Mar 31, 2025
03c68b8
Merge pull request #56 from gruntsoftware/feature/add-version-issue-108
kcw-grunt Apr 1, 2025
c770f01
Revert "Updated the APIManager"
kcw-grunt Mar 31, 2025
f8c1bd5
Reverted the Kotlin APIManager.
kcw-grunt Mar 31, 2025
ff58b57
fix: fix crash when FragmentSignal dismissed
andhikayuana Apr 4, 2025
d64ea93
fix: fix sync after wipe
andhikayuana Apr 7, 2025
277f387
Merge branch 'hotfix/api-manager-fix' of https://github.com/brainwall…
kcw-grunt Apr 7, 2025
9a8a20f
fix: fix wrong lifecycle to trigger callback at FragmentSignal
andhikayuana Apr 7, 2025
832fa36
Update issue templates
kcw-grunt Apr 8, 2025
b294cd6
updated core changes
kcw-grunt Apr 8, 2025
4ed8af3
Merge pull request #57 from gruntsoftware/hotfix/api-manager-fix
kcw-grunt Apr 8, 2025
089995a
Rename .java to .kt
andhikayuana Apr 11, 2025
7e85987
chore: refactor BRApiManager & APIClient
andhikayuana Apr 11, 2025
0aa41e7
chore: remove unused part at APIClient
andhikayuana Apr 14, 2025
0cc29cb
feat: wip new peer discovery
andhikayuana Apr 14, 2025
54d2d9e
feat: implement selected peer ip address from cache (fetched from API)
andhikayuana Apr 14, 2025
c6923b1
feat: implement selected peer ip address from cache (fetched from API)
andhikayuana Apr 14, 2025
ef4e6d2
feat: filter out peers with NODE_NETWORK, NODE_BLOOM
andhikayuana Apr 15, 2025
2336939
fix: race condition when clear shared prefs values after wipeAll
andhikayuana Apr 16, 2025
51a4d9a
Merge pull request #62 from gruntsoftware/hotfix/prefs-race-condition
kcw-grunt Apr 16, 2025
62afb9d
Merge pull request #61 from gruntsoftware/feat/new-peer-discovery
kcw-grunt Apr 16, 2025
391e319
Updating the core library
kcw-grunt Apr 16, 2025
836b6f0
Chore/revert pre peer discovery (Android) (#69)
andhikayuana Apr 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]

**Additional context**
Add any other context about the problem here.
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
9 changes: 5 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ android {
applicationId = "ltd.grunt.brainwallet"
minSdk = 29
targetSdk = 34
versionCode = 202503281
versionName = "v4.4.1"
versionCode = 202504251
versionName = "v4.4.7"

multiDexEnabled = true
base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})")
Expand Down Expand Up @@ -188,7 +188,6 @@ dependencies {
}

implementation("androidx.webkit:webkit:1.9.0")
implementation("com.squareup.moshi:moshi-kotlin:1.15.2")
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.legacy.support)
Expand Down Expand Up @@ -217,7 +216,9 @@ dependencies {
implementation(platform(libs.koin.bom))
implementation(libs.bundles.koin)

implementation(libs.squareup.okhttp)
implementation(platform(libs.squareup.okhttp.bom))
implementation(libs.bundles.squareup.okhttp)
implementation(libs.bundles.squareup.retrofit)
implementation(libs.jakewarthon.timber)
implementation(libs.commons.io)
implementation(libs.bundles.eclipse.jetty)
Expand Down
25 changes: 0 additions & 25 deletions app/src/main/java/com/brainwallet/data/model/CurrencyEntity.java

This file was deleted.

17 changes: 17 additions & 0 deletions app/src/main/java/com/brainwallet/data/model/CurrencyEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.brainwallet.data.model

import kotlinx.serialization.SerialName
import java.io.Serializable

@kotlinx.serialization.Serializable
data class CurrencyEntity(
@JvmField
var code: String ="",
@JvmField
var name: String = "",
@JvmField
@SerialName("n")
var rate: Float = 0F,
@JvmField
var symbol: String = ""
) : Serializable
37 changes: 37 additions & 0 deletions app/src/main/java/com/brainwallet/data/model/Fee.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.brainwallet.data.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class Fee(
@JvmField
@SerialName("fee_per_kb")
var luxury: Long,
@JvmField
@SerialName("fee_per_kb_economy")
var regular: Long,
@JvmField
@SerialName("fee_per_kb_luxury")
var economy: Long,
var timestamp: Long
) {
companion object {
//from legacy
// this is the default that matches the mobile-api if the server is unavailable
private const val defaultEconomyFeePerKB: Long =
2500L // From legacy minimum. default min is 1000 as Litecoin Core version v0.17.1
private const val defaultRegularFeePerKB: Long = 25000L
private const val defaultLuxuryFeePerKB: Long = 66746L
private const val defaultTimestamp: Long = 1583015199122L

@JvmStatic
val Default = Fee(
defaultLuxuryFeePerKB,
defaultRegularFeePerKB,
defaultEconomyFeePerKB,
defaultTimestamp
)

}
}
59 changes: 59 additions & 0 deletions app/src/main/java/com/brainwallet/data/repository/LtcRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.brainwallet.data.repository

import android.content.Context
import com.brainwallet.data.model.CurrencyEntity
import com.brainwallet.data.model.Fee
import com.brainwallet.data.source.RemoteApiSource
import com.brainwallet.tools.manager.BRSharedPrefs
import com.brainwallet.tools.manager.FeeManager
import com.brainwallet.tools.sqlite.CurrencyDataSource

interface LtcRepository {
suspend fun fetchRates(): List<CurrencyEntity>

suspend fun fetchFeePerKb(): Fee

class Impl(
private val context: Context,
private val remoteApiSource: RemoteApiSource,
private val currencyDataSource: CurrencyDataSource
) : LtcRepository {

//todo: make it offline first here later, currently just using CurrencyDataSource.getAllCurrencies
override suspend fun fetchRates(): List<CurrencyEntity> {
return runCatching {
val rates = remoteApiSource.getRates()

//legacy logic
FeeManager.updateFeePerKb(context)
val selectedISO = BRSharedPrefs.getIsoSymbol(context)
rates.forEachIndexed { index, currencyEntity ->
if (currencyEntity.code.equals(selectedISO, ignoreCase = true)) {
BRSharedPrefs.putIso(context, currencyEntity.code)
BRSharedPrefs.putCurrencyListPosition(context, index - 1)
}
}

//save to local
currencyDataSource.putCurrencies(rates)
return rates
}.getOrElse { currencyDataSource.getAllCurrencies(true) }

}

override suspend fun fetchFeePerKb(): Fee {
return runCatching {
val fee = remoteApiSource.getFeePerKb()

//todo: cache

return fee
}.getOrElse { Fee.Default }
}

}

companion object {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.brainwallet.data.repository

import android.content.SharedPreferences
import androidx.core.content.edit
import com.brainwallet.di.json
import kotlinx.serialization.json.jsonObject
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Response
import okio.IOException
import timber.log.Timber
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

interface SelectedPeersRepository {

suspend fun fetchSelectedPeers(): Set<String>

class Impl(
private val okHttpClient: OkHttpClient,
private val sharedPreferences: SharedPreferences,
) : SelectedPeersRepository {

private companion object {
const val PREF_KEY_SELECTED_PEERS = "selected_peers"
const val PREF_KEY_SELECTED_PEERS_CACHED_AT = "${PREF_KEY_SELECTED_PEERS}_cached_at"
}

override suspend fun fetchSelectedPeers(): Set<String> {
val lastUpdateTime = sharedPreferences.getLong(PREF_KEY_SELECTED_PEERS_CACHED_AT, 0)
val currentTime = System.currentTimeMillis()
val cachedPeers = sharedPreferences.getStringSet(PREF_KEY_SELECTED_PEERS, null)

// Check if cache exists and is less than 6 hours old
if (!cachedPeers.isNullOrEmpty() && (currentTime - lastUpdateTime) < 6 * 60 * 60 * 1000) {
return cachedPeers
}

val request = okhttp3.Request.Builder()
.url(LITECOIN_NODES_URL)
.build()

return suspendCoroutine { continuation ->
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
continuation.resume(emptySet()) //just return empty if failed or need hardcoded?
}

override fun onResponse(call: Call, response: Response) {
val jsonString = response.body?.string()

if (response.isSuccessful.not()) {
continuation.resume(cachedPeers ?: emptySet())
return
}

val parsedResult = jsonString?.let {
val jsonElement = json.parseToJsonElement(it)
val dataObject = jsonElement.jsonObject["data"]?.jsonObject
val nodesObject = dataObject?.get("nodes")?.jsonObject

//filter criteria
val requiredServices = 0x01 or 0x04 // NODE_NETWORK | NODE_BLOOM

nodesObject?.entries
?.filter { entry ->
val flags =
entry.value.jsonObject["flags"]?.toString()?.toIntOrNull()
flags != null && (flags and requiredServices) == requiredServices
}
?.map { it.key.replace(":9333", "") }
?.toSet().also { Timber.d("Total Selected Peers ${it?.size}") }
?: emptySet()

} ?: emptySet()

sharedPreferences.edit {
putStringSet(PREF_KEY_SELECTED_PEERS, parsedResult)
putLong(PREF_KEY_SELECTED_PEERS_CACHED_AT, currentTime)
}

continuation.resume(parsedResult)
}
})
}
}

}

companion object {
const val LITECOIN_NODES_URL = "https://api.blockchair.com/litecoin/nodes"
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/com/brainwallet/data/source/RemoteApiSource.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.brainwallet.data.source

import com.brainwallet.data.model.CurrencyEntity
import com.brainwallet.data.model.Fee
import retrofit2.http.GET

//TODO
interface RemoteApiSource {

@GET("v1/rates")
suspend fun getRates(): List<CurrencyEntity>

@GET("v1/fee-per-kb")
suspend fun getFeePerKb(): Fee

// https://prod.apigsltd.net/moonpay/buy?address=ltc1qjnsg3p9rt4r4vy7ncgvrywdykl0zwhkhcp8ue0&code=USD&idate=1742331930290&uid=ec51fa950b271ff3
// suspend fun getMoonPayBuy()
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ interface RemoteConfigSource {

companion object {
const val KEY_FEATURE_MENU_HIDDEN_EXAMPLE = "feature_menu_hidden_example"
const val KEY_API_BASEURL_PROD_NEW_ENABLED = "key_api_baseurl_prod_new_enabled"
const val KEY_API_BASEURL_DEV_NEW_ENABLED = "key_api_baseurl_dev_new_enabled"
const val KEY_KEYSTORE_MANAGER_ENABLED = "key_keystore_manager_enabled"
const val KEY_FEATURE_SELECTED_PEERS_ENABLED = "feature_selected_peers_enabled"
}

fun initialize()
Expand Down
Loading