Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
/.superpowers/*
/docs/plans/*
/.playwright-mcp/*
/.gemini/*
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ android {
buildConfigField "String", "STELLAR_KIT_VERSION", "\"${libs.versions.stellar.kit.get()}\""
buildConfigField "String", "ZCASH_SDK_VERSION", "\"${libs.versions.zcashAndroidSdk.get()}\""
buildConfigField "String", "GIT_BRANCH", "\"${gitBranchProvider.getOrElse("unknown")}\""
buildConfigField "boolean", "PAYCORE_ENABLED", "true"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

ksp {
Expand All @@ -93,6 +94,8 @@ android {
resValue "string", "appTelegramLink", "https://t.me/pcash"
resValue "string", "appRedditLink", "https://www.reddit.com/r/PirateCash/"
resValue "string", "reportEmail", "i@p.cash"
resValue "string", "payCoreSupportEmail", "support@paycore.pw"
resValue "string", "payCoreSupportUrl", "https://t.me/paycore_supp"
resValue "string", "walletConnectAppMetaDataName", "pcash"
resValue "string", "walletConnectAppMetaDataUrl", "p.cash"
resValue "string", "walletConnectAppMetaDataIcon", "https://raw.githubusercontent.com/piratecash/pcash-wallet-ios/master/UnstoppableWallet/UnstoppableWallet/AppIcon.xcassets/AppIcon.appiconset/p.cash_1024.png"
Expand Down
240 changes: 100 additions & 140 deletions app/detekt-baseline.xml

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions app/src/main/java/cash/p/terminal/core/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ import io.horizontalsystems.core.IAppNumberFormatter
import io.horizontalsystems.core.ICoreApp
import io.horizontalsystems.core.entities.BlockchainType
import io.horizontalsystems.core.logger.AppLog
import io.horizontalsystems.core.security.EncryptionManager
import io.horizontalsystems.core.security.KeyStoreManager
import io.horizontalsystems.ethereumkit.core.EthereumKit
import io.reactivex.plugins.RxJavaPlugins
Expand Down Expand Up @@ -283,7 +282,7 @@ class App : CoreApp(), WorkConfiguration.Provider, SingletonImageLoader.Factory
keyProvider = this
}

encryptionManager = EncryptionManager(keyProvider)
encryptionManager = get()

systemInfoManager = get()

Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/cash/p/terminal/core/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,15 @@ import org.koin.core.parameter.ParametersDefinition
import org.koin.java.KoinJavaComponent.inject
import java.io.File
import java.math.BigDecimal
import java.net.URI
import java.net.NoRouteToHostException
import java.net.UnknownHostException
import java.nio.channels.UnresolvedAddressException
import java.util.Locale
import java.util.Optional

import androidx.compose.ui.focus.FocusManager
import kotlinx.coroutines.CoroutineScope
import java.net.URI

/**
* Clears focus (dismissing keyboard) and launches [action] after a brief delay.
Expand All @@ -81,6 +84,16 @@ fun CoroutineScope.launchAfterClearingFocus(
}
}

fun Throwable.isNoInternetException(): Boolean {
return generateSequence(this) { it.cause }
.any { throwable ->
throwable is UnknownHostException ||
throwable is NoRouteToHostException ||
throwable is UnresolvedAddressException ||
throwable.message.equals("No internet connection", ignoreCase = true)
}
}

fun String.orHide(hidden: Boolean, hideValue: String = "*****"): String =
if (hidden) hideValue else this

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/cash/p/terminal/core/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cash.p.terminal.di.managerModule
import cash.p.terminal.di.repositoryModule
import cash.p.terminal.di.storageModule
import cash.p.terminal.di.swapProvidersModule
import cash.p.terminal.modules.paycore.payCoreModule
import cash.p.terminal.di.viewModelModule
import cash.p.terminal.feature.logging.di.featureLoggingModule
import cash.p.terminal.feature.miniapp.di.featureMiniAppModule
Expand All @@ -27,6 +28,7 @@ val appModule = module {
featureTrezorModule,
networkModule,
swapProvidersModule,
payCoreModule,
contractValidatorModule,
useCaseModule,
appUpdateModule,
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/cash/p/terminal/core/di/UseCaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import cash.p.terminal.core.usecase.GenerateMoneroWalletUseCase
import cash.p.terminal.core.usecase.GetMoneroWalletFilesNameUseCase
import cash.p.terminal.core.usecase.MoneroWalletUseCase
import cash.p.terminal.core.usecase.FetchSwapQuotesUseCase
import cash.p.terminal.core.usecase.ResolvePayCoreNavigationUseCase
import cash.p.terminal.core.usecase.ResolveTransactionItemUseCase
import cash.p.terminal.core.usecase.SyncPendingMultiSwapUseCase
import cash.p.terminal.core.usecase.UpdateSwapProviderTransactionsStatusUseCase
import cash.p.terminal.core.usecase.ValidateMoneroHeightUseCase
Expand All @@ -35,6 +37,8 @@ val useCaseModule = module {
singleOf(::UpdateSwapProviderTransactionsStatusUseCase)
singleOf(::SyncPendingMultiSwapUseCase)
factoryOf(::FetchSwapQuotesUseCase)
factoryOf(::ResolveTransactionItemUseCase)
factoryOf(::ResolvePayCoreNavigationUseCase)
factoryOf(::ValidateMoneroMnemonicUseCase)
factoryOf(::ValidateMoneroHeightUseCase)
factoryOf(::GetLocalizedAssetUseCase)
Expand Down
22 changes: 9 additions & 13 deletions app/src/main/java/cash/p/terminal/core/managers/FaqManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import cash.p.terminal.modules.markdown.MarkdownFragment
import cash.p.terminal.modules.markdown.localreader.MarkdownLocalFragment
import cash.p.terminal.navigation.slideFromBottom
import io.horizontalsystems.core.BackgroundManager
import io.reactivex.Single
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Request
import timber.log.Timber
import java.lang.reflect.Type
Expand Down Expand Up @@ -75,19 +76,14 @@ object FaqManager {
return navHost?.navController
}

fun getFaqList(): Single<List<FaqMap>> {
return Single.fromCallable {
val request = Request.Builder()
.url(faqListUrl)
.build()
suspend fun getFaqList(): List<FaqMap> = withContext(Dispatchers.IO) {
val request = Request.Builder()
.url(faqListUrl)
.build()

val response = APIClient.okHttpClient.newCall(request).execute()

val listType = object : TypeToken<List<FaqMap>>() {}.type
val list: List<FaqMap> = gson.fromJson(response.body?.charStream(), listType)
response.close()

list
val listType = object : TypeToken<List<FaqMap>>() {}.type
APIClient.okHttpClient.newCall(request).execute().use { response ->
gson.fromJson(response.body?.charStream(), listType)
}
}

Expand Down
18 changes: 6 additions & 12 deletions app/src/main/java/cash/p/terminal/core/managers/GuidesManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import com.google.gson.*
import cash.p.terminal.core.providers.AppConfigProvider
import cash.p.terminal.entities.Guide
import cash.p.terminal.entities.GuideCategoryMultiLang
import io.reactivex.Single
import okhttp3.OkHttpClient
import okhttp3.Request
import java.lang.reflect.Type
import java.net.URL
Expand All @@ -20,17 +18,13 @@ object GuidesManager {
.registerTypeAdapter(Guide::class.java, GuideDeserializer(guidesUrl))
.create()

fun getGuideCategories(): Single<Array<GuideCategoryMultiLang>> {
return Single.fromCallable {
val request = Request.Builder()
.url(guidesUrl)
.build()
fun getGuideCategories(): Array<GuideCategoryMultiLang> {
val request = Request.Builder()
.url(guidesUrl)
.build()

val response = APIClient.okHttpClient.newCall(request).execute()
val categories = gson.fromJson(response.body?.charStream(), Array<GuideCategoryMultiLang>::class.java)
response.close()

categories
return APIClient.okHttpClient.newCall(request).execute().use { response ->
gson.fromJson(response.body?.charStream(), Array<GuideCategoryMultiLang>::class.java)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ object AppConfigProvider {
val appTelegramLink by lazy { Translator.getString(R.string.appTelegramLink) }
val appRedditLink by lazy { Translator.getString(R.string.appRedditLink) }
val reportEmail by lazy { Translator.getString(R.string.reportEmail) }
val payCoreSupportEmail by lazy { Translator.getString(R.string.payCoreSupportEmail) }
val payCoreSupportUrl by lazy { Translator.getString(R.string.payCoreSupportUrl) }
const val mempoolSpaceUrl: String = "https://mempool.space"
const val blockCypherUrl: String = "https://api.blockcypher.com"
const val walletConnectUrl = "relay.walletconnect.com"
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/java/cash/p/terminal/core/storage/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import cash.p.terminal.core.storage.migrations.Migration_97_98
import cash.p.terminal.core.storage.migrations.Migration_98_99
import cash.p.terminal.core.storage.migrations.Migration_99_100
import cash.p.terminal.core.storage.migrations.Migration_100_101
import cash.p.terminal.core.storage.migrations.Migration_101_102
import cash.p.terminal.core.storage.migrations.Migration_102_101
import cash.p.terminal.core.storage.typeconverter.DatabaseConverters
import cash.p.terminal.entities.ActiveAccount
import cash.p.terminal.entities.BlockchainSettingRecord
Expand Down Expand Up @@ -112,7 +114,7 @@ import io.horizontalsystems.core.storage.LogEntry
import io.horizontalsystems.core.storage.LogsDao

@Database(
version = 101,
version = 102,
exportSchema = false,
entities = [
EnabledWallet::class,
Expand Down Expand Up @@ -263,7 +265,9 @@ abstract class AppDatabase : RoomDatabase() {
Migration_97_98,
Migration_98_99,
Migration_99_100,
Migration_100_101
Migration_100_101,
Migration_101_102,
Migration_102_101
)
.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ interface SwapProviderTransactionsDao {
@Query("SELECT * FROM SwapProviderTransaction WHERE transactionId = :transactionId")
suspend fun getTransaction(transactionId: String): SwapProviderTransaction?

@Query("SELECT * FROM SwapProviderTransaction WHERE date = :date")
suspend fun getByDate(date: Long): SwapProviderTransaction?

@Query("SELECT * FROM SwapProviderTransaction ORDER BY date DESC LIMIT 100")
fun observeAll(): Flow<List<SwapProviderTransaction>>

@Query("SELECT * FROM SwapProviderTransaction WHERE accountId = :accountId ORDER BY date DESC LIMIT 100")
fun observeAllByAccount(accountId: String): Flow<List<SwapProviderTransaction>>

@Query(
"SELECT * FROM SwapProviderTransaction WHERE " +
"(coinUidIn = :coinUid AND blockchainTypeIn = :blockchainType AND date >= :dateFrom AND date <= :dateTo) " +
Expand Down Expand Up @@ -112,14 +118,26 @@ interface SwapProviderTransactionsDao {
dateTo: Long
): SwapProviderTransaction?

@Query("SELECT * FROM SwapProviderTransaction WHERE date = :date")
fun observeByDate(date: Long): Flow<SwapProviderTransaction?>

@Query("UPDATE SwapProviderTransaction SET incomingRecordUid = :incomingRecordUid, amountOutReal = :amountOutReal WHERE date = :date")
fun setIncomingRecordUid(date: Long, incomingRecordUid: String, amountOutReal: BigDecimal)

@Query("UPDATE SwapProviderTransaction SET outgoingRecordUid = :outgoingRecordUid WHERE date = :date")
fun setOutgoingRecordUid(date: Long, outgoingRecordUid: String)

@Query("UPDATE SwapProviderTransaction SET status = :status, amountOutReal = :amountOutReal, finishedAt = :finishedAt WHERE transactionId = :transactionId")
fun updateStatusFields(transactionId: String, status: String, amountOutReal: BigDecimal?, finishedAt: Long?)
@Query("UPDATE SwapProviderTransaction SET status = :status, amountOutReal = :amountOutReal, finishedAt = :finishedAt WHERE date = :date")
fun updateStatusFields(date: Long, status: String, amountOutReal: BigDecimal?, finishedAt: Long?)

@Query("UPDATE SwapProviderTransaction SET transactionId = :newTransactionId WHERE date = :date")
fun updateTransactionId(date: Long, newTransactionId: String)

@Query("DELETE FROM SwapProviderTransaction WHERE date = :date")
fun deleteByDate(date: Long)

@Query("DELETE FROM SwapProviderTransaction WHERE transactionId = :transactionId")
fun deleteByTransactionId(transactionId: String)

@Query(
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package cash.p.terminal.core.storage
import cash.p.terminal.core.utils.SwapTransactionMatcher
import cash.p.terminal.entities.SwapProviderTransaction
import cash.p.terminal.network.swaprepository.SwapProvider
import cash.p.terminal.wallet.ActiveAccountState
import cash.p.terminal.wallet.Token
import io.horizontalsystems.core.DispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.withContext
import java.math.BigDecimal

Expand Down Expand Up @@ -52,11 +55,29 @@ class SwapProviderTransactionsStorage(

fun observeAll(): Flow<List<SwapProviderTransaction>> = dao.observeAll()

fun observeAllByAccount(accountId: String): Flow<List<SwapProviderTransaction>> =
dao.observeAllByAccount(accountId)

fun observeForActiveAccount(
activeAccountStateFlow: Flow<ActiveAccountState>
): Flow<List<SwapProviderTransaction>> =
activeAccountStateFlow.flatMapLatest { state ->
val accountId = (state as? ActiveAccountState.ActiveAccount)?.account?.id
if (accountId != null) dao.observeAllByAccount(accountId) else flowOf(emptyList())
}

fun observeByDate(date: Long): Flow<SwapProviderTransaction?> = dao.observeByDate(date)

suspend fun getTransaction(transactionId: String) =
withContext(dispatcherProvider.io) {
dao.getTransaction(transactionId)
}

suspend fun getByDate(date: Long): SwapProviderTransaction? =
withContext(dispatcherProvider.io) {
dao.getByDate(date)
}

fun getByCoinUidIn(
coinUid: String,
blockchainType: String,
Expand Down Expand Up @@ -118,11 +139,14 @@ class SwapProviderTransactionsStorage(
dao.setOutgoingRecordUid(date, outgoingRecordUid)

fun updateStatusFields(
transactionId: String,
date: Long,
status: String,
amountOutReal: BigDecimal?,
finishedAt: Long?
) = dao.updateStatusFields(transactionId, status, amountOutReal, finishedAt)
) = dao.updateStatusFields(date, status, amountOutReal, finishedAt)

fun updateTransactionId(date: Long, newTransactionId: String) =
dao.updateTransactionId(date, newTransactionId)

fun getByProviderAndTokenOut(
provider: SwapProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cash.p.terminal.core.storage.migrations

import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

object Migration_101_102 : Migration(101, 102) {
override fun migrate(db: SupportSQLiteDatabase) {
val cursor = db.query("PRAGMA table_info(SwapProviderTransaction)")
val columns = mutableListOf<String>()
while (cursor.moveToNext()) {
columns.add(cursor.getString(cursor.getColumnIndexOrThrow("name")))
}
cursor.close()

if ("accountId" !in columns) {
db.execSQL(
"ALTER TABLE SwapProviderTransaction ADD COLUMN accountId TEXT NOT NULL DEFAULT ''"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cash.p.terminal.core.storage.migrations

import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

object Migration_102_101 : Migration(102, 101) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE `SwapProviderTransaction_tmp` (" +
"`date` INTEGER NOT NULL, " +
"`outgoingRecordUid` TEXT, " +
"`transactionId` TEXT NOT NULL, " +
"`status` TEXT NOT NULL, " +
"`provider` TEXT NOT NULL, " +
"`coinUidIn` TEXT NOT NULL, " +
"`blockchainTypeIn` TEXT NOT NULL, " +
"`amountIn` TEXT NOT NULL, " +
"`addressIn` TEXT NOT NULL, " +
"`coinUidOut` TEXT NOT NULL, " +
"`blockchainTypeOut` TEXT NOT NULL, " +
"`amountOut` TEXT NOT NULL, " +
"`addressOut` TEXT NOT NULL, " +
"`amountOutReal` TEXT, " +
"`finishedAt` INTEGER, " +
"`incomingRecordUid` TEXT, " +
"PRIMARY KEY(`date`))"
)
db.execSQL(
"INSERT INTO `SwapProviderTransaction_tmp` (" +
"`date`, `outgoingRecordUid`, `transactionId`, `status`, `provider`, " +
"`coinUidIn`, `blockchainTypeIn`, `amountIn`, `addressIn`, " +
"`coinUidOut`, `blockchainTypeOut`, `amountOut`, `addressOut`, " +
"`amountOutReal`, `finishedAt`, `incomingRecordUid`) " +
"SELECT " +
"`date`, `outgoingRecordUid`, `transactionId`, `status`, `provider`, " +
"`coinUidIn`, `blockchainTypeIn`, `amountIn`, `addressIn`, " +
"`coinUidOut`, `blockchainTypeOut`, `amountOut`, `addressOut`, " +
"`amountOutReal`, `finishedAt`, `incomingRecordUid` " +
"FROM `SwapProviderTransaction`"
)
db.execSQL("DROP TABLE `SwapProviderTransaction`")
db.execSQL("ALTER TABLE `SwapProviderTransaction_tmp` RENAME TO `SwapProviderTransaction`")
}
}
Loading
Loading