diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b8f91239..3d4cb0e2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "ltd.grunt.brainwallet" minSdk = 29 targetSdk = 34 - versionCode = 202503312 - versionName = "v4.4.3" + versionCode = 202504251 + versionName = "v4.4.7" multiDexEnabled = true base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})") diff --git a/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.kt b/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.kt index 1ccba70c..a46406bc 100644 --- a/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.kt +++ b/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.kt @@ -14,29 +14,4 @@ data class CurrencyEntity( var rate: Float = 0F, @JvmField var symbol: String = "" -) : Serializable { -// @JvmField -// var code: String? = null -// @JvmField -// var name: String? = null -// @JvmField -// var rate: Float = 0f -// @JvmField -// var symbol: String? = null -// -// constructor(code: String?, name: String?, rate: Float, symbol: String?) { -// this.code = code -// this.name = name -// this.rate = rate -// this.symbol = symbol -// } -// -// constructor() -// -// companion object { -// //Change this after modifying the class -// private const val serialVersionUID = 7526472295622776147L -// -// val TAG: String = CurrencyEntity::class.java.name -// } -} +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/data/model/Fee.kt b/app/src/main/java/com/brainwallet/data/model/Fee.kt new file mode 100644 index 00000000..246100a4 --- /dev/null +++ b/app/src/main/java/com/brainwallet/data/model/Fee.kt @@ -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 + ) + + } +} diff --git a/app/src/main/java/com/brainwallet/data/repository/LtcRepository.kt b/app/src/main/java/com/brainwallet/data/repository/LtcRepository.kt index 02358b34..31c05e30 100644 --- a/app/src/main/java/com/brainwallet/data/repository/LtcRepository.kt +++ b/app/src/main/java/com/brainwallet/data/repository/LtcRepository.kt @@ -2,6 +2,7 @@ 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 @@ -9,7 +10,8 @@ import com.brainwallet.tools.sqlite.CurrencyDataSource interface LtcRepository { suspend fun fetchRates(): List - //todo + + suspend fun fetchFeePerKb(): Fee class Impl( private val context: Context, @@ -19,22 +21,39 @@ interface LtcRepository { //todo: make it offline first here later, currently just using CurrencyDataSource.getAllCurrencies override suspend fun fetchRates(): List { - 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) + 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 + //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 { + + } } \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/data/repository/SelectedPeersRepository.kt b/app/src/main/java/com/brainwallet/data/repository/SelectedPeersRepository.kt index 008453a0..0275d542 100644 --- a/app/src/main/java/com/brainwallet/data/repository/SelectedPeersRepository.kt +++ b/app/src/main/java/com/brainwallet/data/repository/SelectedPeersRepository.kt @@ -50,6 +50,11 @@ interface SelectedPeersRepository { 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 diff --git a/app/src/main/java/com/brainwallet/data/source/RemoteApiSource.kt b/app/src/main/java/com/brainwallet/data/source/RemoteApiSource.kt index 0ce3bdc8..60c865e2 100644 --- a/app/src/main/java/com/brainwallet/data/source/RemoteApiSource.kt +++ b/app/src/main/java/com/brainwallet/data/source/RemoteApiSource.kt @@ -1,16 +1,17 @@ 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("api/v1/rates") + @GET("v1/rates") suspend fun getRates(): List @GET("v1/fee-per-kb") - suspend fun getFeePerKb() + suspend fun getFeePerKb(): Fee // https://prod.apigsltd.net/moonpay/buy?address=ltc1qjnsg3p9rt4r4vy7ncgvrywdykl0zwhkhcp8ue0&code=USD&idate=1742331930290&uid=ec51fa950b271ff3 // suspend fun getMoonPayBuy() diff --git a/app/src/main/java/com/brainwallet/di/Module.kt b/app/src/main/java/com/brainwallet/di/Module.kt index e64e071a..4dce30f1 100644 --- a/app/src/main/java/com/brainwallet/di/Module.kt +++ b/app/src/main/java/com/brainwallet/di/Module.kt @@ -4,10 +4,10 @@ import android.content.Context import android.content.SharedPreferences import com.brainwallet.BuildConfig import com.brainwallet.data.repository.LtcRepository +import com.brainwallet.data.repository.SelectedPeersRepository import com.brainwallet.data.repository.SettingRepository import com.brainwallet.data.source.RemoteApiSource import com.brainwallet.data.source.RemoteConfigSource -import com.brainwallet.data.repository.SelectedPeersRepository import com.brainwallet.tools.sqlite.CurrencyDataSource import com.brainwallet.tools.util.BRConstants import com.brainwallet.ui.screens.home.SettingsViewModel @@ -31,6 +31,7 @@ import org.koin.android.ext.koin.androidApplication import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module +import retrofit2.HttpException import retrofit2.Retrofit import retrofit2.converter.kotlinx.serialization.asConverterFactory @@ -46,7 +47,7 @@ val dataModule = module { factory { provideOkHttpClient() } single { provideRetrofit(get(), BRConstants.BW_API_PROD_HOST) } - single { provideApi(get()) } + single { provideApi(get()) } single { RemoteConfigSource.FirebaseImpl(Firebase.remoteConfig).also { @@ -91,17 +92,25 @@ private fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder() .addHeader("Content-Type", "application/json") .addHeader("X-Litecoin-Testnet", "false") .addHeader("Accept-Language", "en") -// .addHeader("User-agent",) chain.proceed(requestBuilder.build()) } .addInterceptor { chain -> val request = chain.request() runCatching { - chain.proceed(request) + val result = chain.proceed(request) + if (result.isSuccessful.not()) { + throw HttpException( + retrofit2.Response.error( + result.code, + result.body ?: result.peekBody(Long.MAX_VALUE) + ) + ) + } + result }.getOrElse { //retry using dev host val newRequest = request.newBuilder() - .url(BRConstants.BW_API_DEV_HOST + request.url.encodedPath) + .url("${BRConstants.LEGACY_BW_API_DEV_HOST}/api${request.url.encodedPath}") //legacy dev api need prefix path /api .build() chain.proceed(newRequest) } @@ -129,5 +138,5 @@ internal fun provideRetrofit( ) .build() -internal fun provideApi(retrofit: Retrofit): RemoteApiSource = - retrofit.create(RemoteApiSource::class.java) \ No newline at end of file +internal inline fun provideApi(retrofit: Retrofit): T = + retrofit.create(T::class.java) \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/presenter/entities/Fee.java b/app/src/main/java/com/brainwallet/presenter/entities/Fee.java deleted file mode 100644 index bbe80465..00000000 --- a/app/src/main/java/com/brainwallet/presenter/entities/Fee.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.brainwallet.presenter.entities; - -public class Fee { - public final long luxury; - public final long regular; - public final long economy; - public final long timestamp; - - public Fee(long luxury, long regular, long economy, long timestamp) { - this.luxury = luxury; - this.regular = regular; - this.economy = economy; - this.timestamp = timestamp; - } -} diff --git a/app/src/main/java/com/brainwallet/tools/manager/BRSharedPrefs.java b/app/src/main/java/com/brainwallet/tools/manager/BRSharedPrefs.java index a3edc499..6fd054b9 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/BRSharedPrefs.java +++ b/app/src/main/java/com/brainwallet/tools/manager/BRSharedPrefs.java @@ -3,15 +3,17 @@ import android.content.Context; import android.content.SharedPreferences; -import com.brainwallet.BrainwalletApp; +import android.util.Log; + import com.brainwallet.data.repository.SettingRepository; import com.brainwallet.tools.util.BRConstants; import org.koin.java.KoinJavaComponent; -import org.koin.mp.KoinPlatformTools_jvmKt; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Currency; +import java.util.Date; import java.util.List; import java.util.Locale; @@ -45,16 +47,14 @@ public static String getIsoSymbol(Context context) { try { if (defaultLanguage == "ru") { defIso = Currency.getInstance(new Locale("ru", "RU")).getCurrencyCode(); - } - else if (defaultLanguage == "en") { + } else if (defaultLanguage == "en") { defIso = Currency.getInstance(Locale.US).getCurrencyCode(); - } - else { + } else { defIso = Currency.getInstance(Locale.getDefault()).getCurrencyCode(); } } catch (IllegalArgumentException e) { Timber.e(e); - defIso = Currency.getInstance(Locale.US).getCurrencyCode(); + defIso = Currency.getInstance(Locale.US).getCurrencyCode(); } return settingsToGet.getString(SettingRepository.KEY_FIAT_CURRENCY_CODE, defIso); //using new shared prefs used by setting repository } @@ -74,38 +74,64 @@ public static void notifyIsoChanged(String iso) { } } - ////////////////////////////////////////////////////////////////////////////// - //////////////////// Active Shared Preferences /////////////////////////////// - public static void putLastSyncTimestamp(Context activity, long time) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + /// /////////////////////////////////////////////////////////////////////////// + /// ///////////////// Active Shared Preferences /////////////////////////////// + + public static void putStartSyncTimestamp(Context context, long time) { + if (context == null) { + Log.e("BRSharedPrefs", "Context is null in putStartSyncTimestamp!"); + return; + } + SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - editor.putLong("lastSyncTime", time); + editor.putLong("startSyncTime", time); editor.apply(); } - public static long getLastSyncTimestamp(Context activity) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getLong("lastSyncTime", 0L); + + public static long getStartSyncTimestamp(Context context) { + if (context == null) { + Log.e("BRSharedPrefs", "Context is null in getStartSyncTimestamp!"); + } + SharedPreferences startSyncTime = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + return startSyncTime.getLong("startSyncTime", System.currentTimeMillis()); } - public static void putStartSyncTimestamp(Context activity, long time) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + + public static void putEndSyncTimestamp(Context context, long time) { + + if (context == null) { + Log.e("BRSharedPrefs", "Context is null in putEndSyncTimestamp!"); + return; + } + + SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - editor.putLong("startSyncTime", time); + editor.putLong("endSyncTime", time); editor.apply(); } - public static long getStartSyncTimestamp(Context activity) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getLong("startSyncTime", 0L); + + public static String getSyncMetadata(Context context) { + SharedPreferences syncMetadata = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + return syncMetadata.getString("syncMetadata", " No Sync Duration metadata"); } - public static void putSyncTimeElapsed(Context activity, long time) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + public static void putSyncMetadata(Context activity, long startSyncTime, long endSyncTime) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, 0); SharedPreferences.Editor editor = prefs.edit(); - editor.putLong("syncTimeElapsed", time); + + double syncDuration = (double) (endSyncTime - startSyncTime) / 1_000.0 / 60.0; + + SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm"); + Date startDate = new Date(startSyncTime); + Date endDate = new Date(endSyncTime); + + String formattedMetadata = String.format("Duration: %3.2f mins\nStarted: %d (%s)\nEnded: %d (%s)", syncDuration, startSyncTime, sdf.format(startDate), endSyncTime, sdf.format(endDate)); + editor.putString("syncMetadata", formattedMetadata); editor.apply(); } - public static long getSyncTimeElapsed(Context activity) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getLong("syncTimeElapsed", 0L); + + public static long getEndSyncTimestamp(Context context) { + SharedPreferences endSyncTime = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + return endSyncTime.getLong("endSyncTime", System.currentTimeMillis()); } public static boolean getPhraseWroteDown(Context context) { @@ -232,9 +258,10 @@ public static void putUseFingerprint(Context activity, boolean use) { editor.putBoolean("useFingerprint", use); editor.apply(); } + public static int getStartHeight(Context context) { SharedPreferences settingsToGet = context.getSharedPreferences(BRConstants.PREFS_NAME, 0); - return settingsToGet.getInt(BRConstants.START_HEIGHT, 0); + return settingsToGet.getInt(BRConstants.START_HEIGHT, 0); } public static void putStartHeight(Context context, int startHeight) { diff --git a/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java b/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java index 91e12a9e..963a56af 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java +++ b/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java @@ -4,13 +4,14 @@ import androidx.annotation.StringDef; +import com.brainwallet.data.repository.LtcRepository; import com.brainwallet.presenter.entities.ServiceItems; +import com.brainwallet.tools.threads.BRExecutor; import com.brainwallet.tools.util.Utils; -import com.brainwallet.presenter.entities.Fee; +import com.brainwallet.data.model.Fee; import com.platform.APIClient; -import org.json.JSONException; -import org.json.JSONObject; +import org.koin.java.KoinJavaComponent; import java.io.IOException; import java.lang.annotation.Retention; @@ -24,16 +25,9 @@ import okhttp3.Response; import timber.log.Timber; - +//we are still using this, maybe in the future will deprecate? public final class FeeManager { - // this is the default that matches the mobile-api if the server is unavailable - private static final long defaultEconomyFeePerKB = 2_500L; // From legacy minimum. default min is 1000 as Litecoin Core version v0.17.1 - private static final long defaultRegularFeePerKB = 2_5000L; - private static final long defaultLuxuryFeePerKB = 66_746L; - private static final long defaultTimestamp = 1583015199122L; - - private Fee defaultValues = new Fee(defaultLuxuryFeePerKB, defaultRegularFeePerKB, defaultEconomyFeePerKB, defaultTimestamp); private static final FeeManager instance; @@ -50,7 +44,7 @@ public static FeeManager getInstance() { } private void initWithDefaultValues() { - currentFees = defaultValues; + currentFees = Fee.getDefault(); feeType = REGULAR; } @@ -75,22 +69,33 @@ public boolean isRegularFee() { public void setFees(long luxuryFee, long regularFee, long economyFee) { // TODO: to be implemented when feePerKB API will be ready + currentFees = new Fee(luxuryFee, regularFee, economyFee, System.currentTimeMillis()); } public static void updateFeePerKb(Context app) { - String jsonString = "{'fee_per_kb': 10000, 'fee_per_kb_economy': 2500, 'fee_per_kb_luxury': 66746}"; - try { - JSONObject obj = new JSONObject(jsonString); - // TODO: Refactor when mobile-api v0.4.0 is in prod - long regularFee = obj.optLong("fee_per_kb"); - long economyFee = obj.optLong("fee_per_kb_economy"); - long luxuryFee = obj.optLong("fee_per_kb_luxury"); - FeeManager.getInstance().setFees(luxuryFee, regularFee, economyFee); +// String jsonString = "{'fee_per_kb': 10000, 'fee_per_kb_economy': 2500, 'fee_per_kb_luxury': 66746}"; +// try { +// JSONObject obj = new JSONObject(jsonString); +// // TODO: Refactor when mobile-api v0.4.0 is in prod +// long regularFee = obj.optLong("fee_per_kb"); +// long economyFee = obj.optLong("fee_per_kb_economy"); +// long luxuryFee = obj.optLong("fee_per_kb_luxury"); +// FeeManager.getInstance().setFees(luxuryFee, regularFee, economyFee); +// BRSharedPrefs.putFeeTime(app, System.currentTimeMillis()); //store the time of the last successful fee fetch +// } catch (JSONException e) { +// Timber.e(new IllegalArgumentException("updateFeePerKb: FAILED: " + jsonString, e)); +// } + + LtcRepository ltcRepository = KoinJavaComponent.get(LtcRepository.class); + BRExecutor.getInstance().executeSuspend( + (coroutineScope, continuation) -> ltcRepository.fetchFeePerKb(continuation) + ).whenComplete((fee, throwable) -> { + + //legacy logic + FeeManager.getInstance().setFees(fee.luxury, fee.regular, fee.economy); BRSharedPrefs.putFeeTime(app, System.currentTimeMillis()); //store the time of the last successful fee fetch - } catch (JSONException e) { - Timber.e(new IllegalArgumentException("updateFeePerKb: FAILED: " + jsonString, e)); - } + }); } // createGETRequestURL diff --git a/app/src/main/java/com/brainwallet/tools/manager/SyncManager.java b/app/src/main/java/com/brainwallet/tools/manager/SyncManager.java index 42e195f5..ace9dcc1 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/SyncManager.java +++ b/app/src/main/java/com/brainwallet/tools/manager/SyncManager.java @@ -1,5 +1,7 @@ package com.brainwallet.tools.manager; +import static com.brainwallet.tools.manager.BRSharedPrefs.putSyncMetadata; + import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; @@ -44,8 +46,6 @@ public synchronized void startSyncingProgressThread(Context app) { } syncTask = new SyncProgressTask(); syncTask.start(); - BRSharedPrefs.putStartSyncTimestamp(app, System.currentTimeMillis()); - BRSharedPrefs.putSyncTimeElapsed(app, 0L); updateStartSyncData(app); } catch (IllegalThreadStateException ex) { Timber.e(ex); @@ -54,41 +54,11 @@ public synchronized void startSyncingProgressThread(Context app) { private synchronized void updateStartSyncData(Context app) { final double progress = BRPeerManager.syncProgress(BRSharedPrefs.getStartHeight(app)); - long startSync = BRSharedPrefs.getStartSyncTimestamp(app); - long lastSync = BRSharedPrefs.getLastSyncTimestamp(app); - long elapsed = BRSharedPrefs.getSyncTimeElapsed(app); - - if (elapsed > 0L) { - elapsed = (System.currentTimeMillis() - lastSync) + elapsed; - } - else { - elapsed = 1L; - } - BRSharedPrefs.putLastSyncTimestamp(app, System.currentTimeMillis()); - BRSharedPrefs.putSyncTimeElapsed(app, elapsed); - double minutesValue = ((double) elapsed / 1_000.0 / 60.0); - String minutesString = String.format( "%3.2f mins", minutesValue); - String millisecString = String.format( "%5d msec", elapsed); - Timber.d("timber: ||\nprogress: %s\nThread: %s\nrunning lastSyncingTime: %s\nelapsed: %s | %s", String.format( "%.2f", progress * 100.00),Thread.currentThread().getName(),String.valueOf(BRSharedPrefs.getLastSyncTimestamp(app)), millisecString, minutesString); - } private synchronized void markFinishedSyncData(Context app) { - Timber.d("timber: || markFinish threadname:%s", Thread.currentThread().getName()); + Timber.d("timber: || SYNC ELAPSE markFinish threadname:%s", Thread.currentThread().getName()); final double progress = BRPeerManager.syncProgress(BRSharedPrefs.getStartHeight(app)); - long startSync = BRSharedPrefs.getStartSyncTimestamp(app); - long lastSync = BRSharedPrefs.getLastSyncTimestamp(app); - long elapsed = BRSharedPrefs.getSyncTimeElapsed(app); - double minutesValue = ((double) elapsed / 1_000.0 / 60.0); - String minutesString = String.format( "%3.2f mins", minutesValue); - String millisecString = String.format( "%5d msec", elapsed); - Timber.d("timber: ||\ncompletedprogress: %s\nstartSyncTime: %s\nlastSyncingTime: %s\ntotalTimeelapsed: %s | %s", String.format( "%.2f", progress * 100.00),String.valueOf(startSync),String.valueOf(lastSync), millisecString, minutesString); - - Bundle params = new Bundle(); - params.putDouble("sync_time_elapsed", minutesValue); - params.putLong("sync_start_timestamp", startSync); - params.putLong("sync_last_timestamp", lastSync); - AnalyticsManager.logCustomEventWithParams(BRConstants._20230407_DCS, params); } public synchronized void stopSyncingProgressThread(Context app) { @@ -139,7 +109,10 @@ public void run() { app = BreadActivity.getApp(); progressStatus = 0; running = true; - Timber.d("timber: run: starting: %s", progressStatus); + long runTimeStamp = System.currentTimeMillis(); + Timber.d("timber: run: starting: %s date: %d", progressStatus, runTimeStamp); + ///Set StartSync + BRSharedPrefs.putStartSyncTimestamp(app, runTimeStamp); if (app != null) { final long lastBlockTimeStamp = BRPeerManager.getInstance().getLastBlockTimestamp() * 1000; @@ -162,6 +135,16 @@ public void run() { progressStatus = BRPeerManager.syncProgress(startHeight); if (progressStatus == 1) { running = false; + /// Record sync time + long startTimeStamp = BRSharedPrefs.getStartSyncTimestamp(app); + long endSyncTimeStamp = System.currentTimeMillis(); + BRSharedPrefs.putEndSyncTimestamp(app, endSyncTimeStamp); + + double syncDuration = (double) (endSyncTimeStamp - startTimeStamp) / 1_000.0 / 60.0; + /// only update if the sync duration is longer than 2 mins + if (syncDuration > 2.0) { + putSyncMetadata(app, startTimeStamp, endSyncTimeStamp); + } continue; } final long lastBlockTimeStamp = BRPeerManager.getInstance().getLastBlockTimestamp() * 1000; diff --git a/app/src/main/java/com/brainwallet/tools/threads/BRExecutor.java b/app/src/main/java/com/brainwallet/tools/threads/BRExecutor.java index ec127999..b1b21255 100644 --- a/app/src/main/java/com/brainwallet/tools/threads/BRExecutor.java +++ b/app/src/main/java/com/brainwallet/tools/threads/BRExecutor.java @@ -1,5 +1,6 @@ package com.brainwallet.tools.threads; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; @@ -7,6 +8,12 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.CoroutineScopeKt; +import kotlinx.coroutines.CoroutineStart; +import kotlinx.coroutines.future.FutureKt; import timber.log.Timber; /* @@ -130,4 +137,13 @@ public Executor forMainThreadTasks() { public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { Timber.d("timber: rejectedExecution: "); } + + public CompletableFuture executeSuspend(kotlin.jvm.functions.Function2, ? extends Object> paramToExec) { + return FutureKt.future( + CoroutineScopeKt.CoroutineScope(EmptyCoroutineContext.INSTANCE), + EmptyCoroutineContext.INSTANCE, + CoroutineStart.DEFAULT, + paramToExec + ); + } } \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/tools/util/BRConstants.java b/app/src/main/java/com/brainwallet/tools/util/BRConstants.java index 5788f6de..1c4d4a53 100644 --- a/app/src/main/java/com/brainwallet/tools/util/BRConstants.java +++ b/app/src/main/java/com/brainwallet/tools/util/BRConstants.java @@ -106,8 +106,8 @@ private BRConstants() { /** * API Hosts */ - public static final String BW_API_PROD_HOST = "https://prod.apigsltd.net"; - public static final String BW_API_DEV_HOST = "https://dev.apigsltd.net"; + public static final String BW_API_PROD_HOST = "https://api.grunt.ltd"; + public static final String LEGACY_BW_API_DEV_HOST = "https://dev.apigsltd.net"; public static final String BLOCK_EXPLORER_BASE_URL = "https://blockchair.com/litecoin/transaction/"; diff --git a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt index 34608ffd..8ef9dfda 100644 --- a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt +++ b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt @@ -32,7 +32,6 @@ import com.brainwallet.ui.screens.inputwords.InputWordsViewModel.Companion.LEGAC import com.brainwallet.ui.screens.inputwords.InputWordsViewModel.Companion.LEGACY_DIALOG_WIPE_ALERT import com.brainwallet.ui.screens.inputwords.InputWordsViewModel.Companion.LEGACY_EFFECT_RESET_PIN import com.brainwallet.ui.screens.yourseedproveit.YourSeedProveItViewModel.Companion.LEGACY_EFFECT_ON_PAPERKEY_PROVED -import com.brainwallet.ui.screens.yourseedwords.YourSeedWordsViewModel.Companion.LEGACY_EFFECT_ON_SAVED_PAPERKEY import com.brainwallet.ui.theme.BrainwalletAppTheme import com.brainwallet.util.EventBus import com.brainwallet.wallet.BRWalletManager @@ -114,10 +113,6 @@ class BrainwalletActivity : BRActivity() { } } - LEGACY_EFFECT_ON_SAVED_PAPERKEY -> { - PostAuth.getInstance().onPhraseProveAuth(this, false) - } - LEGACY_EFFECT_ON_PAPERKEY_PROVED -> { BRSharedPrefs.putPhraseWroteDown(this@BrainwalletActivity, true) LegacyNavigation.startBreadActivity( diff --git a/app/src/main/java/com/brainwallet/ui/screens/home/SettingsEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/home/SettingsEvent.kt index 531305b6..48e0750a 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/home/SettingsEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/home/SettingsEvent.kt @@ -4,7 +4,10 @@ import com.brainwallet.data.model.CurrencyEntity import com.brainwallet.data.model.Language sealed class SettingsEvent { - data class OnLoad(val shareAnalyticsDataEnabled: Boolean = false) : SettingsEvent() + data class OnLoad( + val shareAnalyticsDataEnabled: Boolean = false, + val lastSyncMetadata: String? = null, + ) : SettingsEvent() object OnSecurityUpdatePinClick : SettingsEvent() object OnSecuritySeedPhraseClick : SettingsEvent() object OnSecurityShareAnalyticsDataClick : SettingsEvent() diff --git a/app/src/main/java/com/brainwallet/ui/screens/home/SettingsState.kt b/app/src/main/java/com/brainwallet/ui/screens/home/SettingsState.kt index 3efe10a7..9f924c3c 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/home/SettingsState.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/home/SettingsState.kt @@ -14,4 +14,5 @@ data class SettingsState( val languageSelectorBottomSheetVisible: Boolean = false, val fiatSelectorBottomSheetVisible: Boolean = false, val shareAnalyticsDataEnabled: Boolean = false, + val lastSyncMetadata: String? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/home/SettingsViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/home/SettingsViewModel.kt index 7ec71f98..e9515ce8 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/home/SettingsViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/home/SettingsViewModel.kt @@ -47,7 +47,12 @@ class SettingsViewModel( override fun onEvent(event: SettingsEvent) { when (event) { is SettingsEvent.OnLoad -> viewModelScope.launch { - _state.update { it.copy(shareAnalyticsDataEnabled = event.shareAnalyticsDataEnabled) } + _state.update { + it.copy( + shareAnalyticsDataEnabled = event.shareAnalyticsDataEnabled, + lastSyncMetadata = event.lastSyncMetadata + ) + } } SettingsEvent.OnToggleDarkMode -> viewModelScope.launch { diff --git a/app/src/main/java/com/brainwallet/ui/screens/home/composable/HomeSettingDrawerSheet.kt b/app/src/main/java/com/brainwallet/ui/screens/home/composable/HomeSettingDrawerSheet.kt index 497770f8..42b370a8 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/home/composable/HomeSettingDrawerSheet.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/home/composable/HomeSettingDrawerSheet.kt @@ -6,6 +6,7 @@ import android.util.AttributeSet import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn @@ -57,7 +58,10 @@ fun HomeSettingDrawerSheet( val context = LocalContext.current LaunchedEffect(Unit) { - viewModel.onEvent(SettingsEvent.OnLoad(BRSharedPrefs.getShareData(context))) //currently just load analytics share data here + viewModel.onEvent(SettingsEvent.OnLoad( + shareAnalyticsDataEnabled = BRSharedPrefs.getShareData(context), //currently just load analytics share data here + lastSyncMetadata = BRSharedPrefs.getSyncMetadata(context) //currently just load sync metadata here + )) } /// Layout values @@ -173,6 +177,14 @@ fun HomeSettingDrawerSheet( ) } + item { + SettingRowItem( + modifier = Modifier.height(100.dp), + title = stringResource(R.string.settings_title_sync_metadata), + description = state.lastSyncMetadata ?: "No sync metadata" + ) + } + item { SettingRowItem( title = stringResource(R.string.settings_title_app_version), diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItEvent.kt index d9f530b0..68816d6a 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItEvent.kt @@ -6,6 +6,7 @@ sealed class YourSeedProveItEvent { ) : YourSeedProveItEvent() data class OnDropSeedWordItem( + val index: Int, val expectedWord: String, val actualWord: String ) : YourSeedProveItEvent() diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItScreen.kt index 3e793c6a..171186ab 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItScreen.kt @@ -147,7 +147,7 @@ fun YourSeedProveItScreen( verticalArrangement = Arrangement.spacedBy(horizontalVerticalSpacing.dp), maxItemsInEachRow = maxItemsPerRow ) { - state.correctSeedWords.entries.forEachIndexed { index, (expectedWord, actualWord) -> + state.correctSeedWords.values.forEachIndexed { index, (expectedWord, actualWord) -> val label = if (expectedWord != actualWord && actualWord.isEmpty()) { "${index + 1}" @@ -173,6 +173,7 @@ fun YourSeedProveItScreen( viewModel.onEvent( YourSeedProveItEvent.OnDropSeedWordItem( + index = index, expectedWord = expectedWord, actualWord = text.toString() ) diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItState.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItState.kt index 991763a2..1da5cc92 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItState.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItState.kt @@ -1,7 +1,12 @@ package com.brainwallet.ui.screens.yourseedproveit data class YourSeedProveItState( - val correctSeedWords: Map = mapOf(), + val correctSeedWords: Map = emptyMap(), val shuffledSeedWords: List = emptyList(), val orderCorrected: Boolean = false, ) + +data class SeedWordItem( + val expected: String, + val actual: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItViewModel.kt index 99af1398..6f4d19c4 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedproveit/YourSeedProveItViewModel.kt @@ -18,25 +18,31 @@ class YourSeedProveItViewModel : BrainwalletViewModel() { when (event) { is YourSeedProveItEvent.OnLoad -> _state.update { it.copy( - correctSeedWords = event.seedWords.associateWith { "" }, + correctSeedWords = event.seedWords.mapIndexed { index, word -> + index to SeedWordItem(expected = word) + }.toMap(), shuffledSeedWords = event.seedWords.shuffled() ) } is YourSeedProveItEvent.OnDropSeedWordItem -> _state.update { - val correctSeedWords = it.correctSeedWords.toMutableMap().apply { - this[event.expectedWord] = event.actualWord - } + val correctSeedWords = it.correctSeedWords.map { (index, seedWordItem) -> + if (index == event.index && seedWordItem.expected == event.expectedWord) { + index to seedWordItem.copy(actual = event.actualWord) + } else { + index to seedWordItem + } + }.toMap() it.copy( correctSeedWords = correctSeedWords, - orderCorrected = correctSeedWords.all { (expectedWord, actualWord) -> expectedWord == actualWord } + orderCorrected = correctSeedWords.all { (_, seedWordItem) -> seedWordItem.expected == seedWordItem.actual } ) } YourSeedProveItEvent.OnClear -> _state.update { it.copy( - correctSeedWords = it.correctSeedWords.mapValues { "" } + correctSeedWords = it.correctSeedWords.mapValues { SeedWordItem(expected = it.value.expected) } ) } diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsEvent.kt index 9f9db09b..ba2cbde0 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsEvent.kt @@ -1,5 +1,5 @@ package com.brainwallet.ui.screens.yourseedwords sealed class YourSeedWordsEvent { - object OnSavedItClick : YourSeedWordsEvent() + data class OnSavedItClick(val seedWords: List) : YourSeedWordsEvent() } \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt index bd2e5119..c541f588 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -51,6 +52,15 @@ fun YourSeedWordsScreen( val leadingCopyPadding = 16 val detailLineHeight = 28 + LaunchedEffect(Unit) { + viewModel.uiEffect.collect { effect -> + when (effect) { + is UiEffect.Navigate -> onNavigate.invoke(effect) + else -> Unit + } + } + } + BrainwalletScaffold( topBar = { BrainwalletTopAppBar( @@ -122,7 +132,7 @@ fun YourSeedWordsScreen( LargeButton( onClick = { - viewModel.onEvent(YourSeedWordsEvent.OnSavedItClick) + viewModel.onEvent(YourSeedWordsEvent.OnSavedItClick(seedWords)) }, ) { Text( diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsViewModel.kt index 650a4381..562b8b97 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsViewModel.kt @@ -1,21 +1,18 @@ package com.brainwallet.ui.screens.yourseedwords import androidx.lifecycle.viewModelScope +import com.brainwallet.navigation.Route +import com.brainwallet.navigation.UiEffect import com.brainwallet.ui.BrainwalletViewModel -import com.brainwallet.util.EventBus import kotlinx.coroutines.launch class YourSeedWordsViewModel : BrainwalletViewModel() { override fun onEvent(event: YourSeedWordsEvent) { when (event) { - YourSeedWordsEvent.OnSavedItClick -> viewModelScope.launch { - EventBus.emit(EventBus.Event.Message(LEGACY_EFFECT_ON_SAVED_PAPERKEY)) + is YourSeedWordsEvent.OnSavedItClick -> viewModelScope.launch { + sendUiEffect(UiEffect.Navigate(destinationRoute = Route.YourSeedProveIt(event.seedWords))) } } } - - companion object { - const val LEGACY_EFFECT_ON_SAVED_PAPERKEY = "onSavedPaperKey" - } } \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/wallet/BRPeerManager.java b/app/src/main/java/com/brainwallet/wallet/BRPeerManager.java index 209814e7..6f591402 100644 --- a/app/src/main/java/com/brainwallet/wallet/BRPeerManager.java +++ b/app/src/main/java/com/brainwallet/wallet/BRPeerManager.java @@ -72,7 +72,6 @@ public static void syncStarted() { public static void syncSucceeded() { Context ctx = BrainwalletApp.getBreadContext(); if (ctx == null) return; - BRSharedPrefs.putLastSyncTimestamp(ctx, System.currentTimeMillis()); SyncManager.getInstance().updateAlarms(ctx); BRSharedPrefs.putAllowSpend(ctx, true); SyncManager.getInstance().stopSyncingProgressThread(ctx); @@ -99,9 +98,12 @@ public static void syncFailed() { public static void txStatusUpdate() { Timber.d("timber: txStatusUpdate"); - for (OnTxStatusUpdate listener : statusUpdateListeners) { - if (listener != null) listener.onStatusUpdate(); + synchronized (statusUpdateListeners) { + for (OnTxStatusUpdate listener : statusUpdateListeners) { + if (listener != null) listener.onStatusUpdate(); + } } + BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { @Override public void run() { @@ -192,11 +194,14 @@ public void run() { //wrap logic enable/disable connect with new flow public void wrapConnectV2() { - if (featureSelectedPeersEnabled()) { - fetchSelectedPeers().whenComplete((strings, throwable) -> connect()); - } else { - connect(); - } +// if (featureSelectedPeersEnabled()) { +// fetchSelectedPeers().whenComplete((strings, throwable) -> connect()); +// } else { +// connect(); +// } + //currently we are just using connect(), since the core using hardcoded peers + //https://github.com/gruntsoftware/core/commit/0b7f85feac840c7667338c340c808dfccde4251a + connect(); } public static boolean featureSelectedPeersEnabled() { diff --git a/app/src/main/jni/core b/app/src/main/jni/core index 55b43a86..28d7d31a 160000 --- a/app/src/main/jni/core +++ b/app/src/main/jni/core @@ -1 +1 @@ -Subproject commit 55b43a869300dfc82ead7faa87e71626d580b464 +Subproject commit 28d7d31ae057bdde33b1d19e6b7af3ba07710c89 diff --git a/app/src/main/jni/transition/PeerManager.c b/app/src/main/jni/transition/PeerManager.c index 4c2ce73e..8d249725 100644 --- a/app/src/main/jni/transition/PeerManager.c +++ b/app/src/main/jni/transition/PeerManager.c @@ -385,7 +385,7 @@ Java_com_brainwallet_wallet_BRPeerManager_create(JNIEnv *env, jobject thiz, BRPeerManagerSetCallbacks(_peerManager, NULL, syncStarted, syncStopped, txStatusUpdate, - saveBlocks, savePeers, networkIsReachable, threadCleanup, featureSelectedPeersEnabled, fetchSelectedPeers); + saveBlocks, savePeers, networkIsReachable, threadCleanup); } if (_peerManager == NULL) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4d2d53f..1b2f4e52 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -825,6 +825,7 @@ Unlock Theme App version: + Sync metadata: Sync Sync duration: >20 minutes Game %1$d: