Skip to content

Commit

Permalink
Feature/swap orders (#5288)
Browse files Browse the repository at this point in the history
* Update hint

* Update title color

* Update layout

* Orders

* Update sticky header

* Order detail

* Update share

* Update ripple theme

* Update layout

* Update share scheme

* Update

* Update type string

* Update font weight

* Amounts to include "+" and "-" signs, update title

* Update detail

* Price item

* Add index

* Update index

* Update order

* Update USD collection

* Update share market url

* Update style

* fix typo

* Build message from token

* Fix warnings

* fix typo

---------

Co-authored-by: Crossle Song <[email protected]>
  • Loading branch information
SeniorZhai and crossle authored Jan 23, 2025
1 parent bdbce08 commit 61e3b9b
Show file tree
Hide file tree
Showing 41 changed files with 4,938 additions and 178 deletions.
3,536 changes: 3,536 additions & 0 deletions app/schemas/one.mixin.android.db.MixinDatabase/64.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/src/main/java/one/mixin/android/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ object Constants {
object DataBase {
const val DB_NAME = "mixin.db"
const val MINI_VERSION = 15
const val CURRENT_VERSION = 63
const val CURRENT_VERSION = 64

const val FTS_DB_NAME = "fts.db"
const val PENDING_DB_NAME = "pending.db"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import one.mixin.android.vo.market.GlobalMarket
import one.mixin.android.vo.market.HistoryPrice
import one.mixin.android.vo.market.Market
import one.mixin.android.vo.route.RoutePaymentRequest
import one.mixin.android.vo.route.SwapOrder
import one.mixin.android.vo.sumsub.ProfileResponse
import one.mixin.android.vo.sumsub.RouteTokenResponse
import retrofit2.Call
Expand Down Expand Up @@ -177,6 +178,12 @@ interface RouteService {
@Path("query") query: String,
): MixinResponse<List<Validator>>

@GET("web3/swap/orders")
suspend fun orders(
@Query("offset") offset: String?,
@Query("limit") limit: Int
) : MixinResponse<List<SwapOrder>>

@GET("markets/{id}/price-history")
suspend fun priceHistory(
@Path("id") assetId: String,
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/one/mixin/android/compose/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ fun MixinAppTheme(

@OptIn(ExperimentalMaterialApi::class)
val rippleConfiguration = RippleConfiguration(
color = Color.White,
rippleAlpha = RippleDefaults.rippleAlpha(Color.White, true),
color = if (darkTheme) Color.White else Color.LightGray,
rippleAlpha = RippleDefaults.rippleAlpha(if (darkTheme) Color.White else Color.LightGray, !darkTheme),
)

@OptIn(ExperimentalMaterialApi::class)
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/one/mixin/android/db/MixinDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_59_60
import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_60_61
import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_61_62
import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_62_63
import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_63_64
import one.mixin.android.db.converter.DepositEntryListConverter
import one.mixin.android.db.converter.MembershipConverter
import one.mixin.android.db.converter.MessageStatusConverter
Expand Down Expand Up @@ -121,6 +122,7 @@ import one.mixin.android.vo.market.Market
import one.mixin.android.vo.market.MarketCapRank
import one.mixin.android.vo.market.MarketCoin
import one.mixin.android.vo.market.MarketFavored
import one.mixin.android.vo.route.SwapOrder
import one.mixin.android.vo.safe.DepositEntry
import one.mixin.android.vo.safe.Output
import one.mixin.android.vo.safe.RawTransaction
Expand Down Expand Up @@ -181,7 +183,8 @@ import kotlin.math.min
(MarketCoin::class),
(MarketFavored::class),
(Alert::class),
(MarketCapRank::class)
(MarketCapRank::class),
(SwapOrder::class),
],
version = CURRENT_VERSION,
)
Expand Down Expand Up @@ -277,6 +280,8 @@ abstract class MixinDatabase : RoomDatabase() {

abstract fun marketCapRankDao(): MarketCapRankDao

abstract fun orderDao(): OrderDao

companion object {
private var INSTANCE: MixinDatabase? = null

Expand Down Expand Up @@ -356,6 +361,7 @@ abstract class MixinDatabase : RoomDatabase() {
MIGRATION_60_61,
MIGRATION_61_62,
MIGRATION_62_63,
MIGRATION_63_64,
)
.enableMultiInstanceInvalidation()
.setQueryExecutor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,15 @@ class MixinDatabaseMigrations private constructor() {
db.execSQL("CREATE INDEX IF NOT EXISTS `index_pin_messages_conversation_id_created_at` ON `pin_messages` (`conversation_id`, `created_at`)")
}
}

val MIGRATION_63_64: Migration =
object : Migration(63, 64) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `swap_orders` (`order_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `pay_asset_id` TEXT NOT NULL, `receive_asset_id` TEXT NOT NULL, `pay_amount` TEXT NOT NULL, `receive_amount` TEXT NOT NULL, `pay_trace_id` TEXT NOT NULL, `receive_trace_id` TEXT NOT NULL, `state` TEXT NOT NULL, `created_at` TEXT NOT NULL, `order_type` TEXT NOT NULL, PRIMARY KEY(`order_id`))")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_swap_orders_state_created_at` ON `swap_orders` (`state`, `created_at`)")
}
}

// If you add a new table, be sure to add a clear method to the DatabaseUtil
}
}
55 changes: 55 additions & 0 deletions app/src/main/java/one/mixin/android/db/OrderDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package one.mixin.android.db

import androidx.lifecycle.LiveData
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
import one.mixin.android.ui.home.web3.components.InscriptionState
import one.mixin.android.vo.UtxoItem
import one.mixin.android.vo.route.SwapOrder
import one.mixin.android.vo.route.SwapOrderItem
import one.mixin.android.vo.safe.Output
import one.mixin.android.vo.safe.SafeCollectible
import one.mixin.android.vo.safe.SafeCollection
import timber.log.Timber

@Dao
interface OrderDao : BaseDao<SwapOrder> {

@Query(
"""
SELECT o.*, t.icon_url as asset_icon_url, rt.icon_url as receive_asset_icon_url, rt.symbol as receive_asset_symbol, t.symbol as asset_symbol, pc.name AS pay_chain_name, rc.name AS receive_chain_name FROM swap_orders o
LEFT JOIN tokens t ON o.pay_asset_id = t.asset_id
LEFT JOIN tokens rt ON o.receive_asset_id = rt.asset_id
LEFT JOIN chains pc ON t.chain_id = pc.chain_id
LEFT JOIN chains rc ON rt.chain_id = rc.chain_id
ORDER BY o.created_at DESC
"""
)
fun orders(): Flow<List<SwapOrderItem>>

@Query(
"""
SELECT o.*, t.icon_url as asset_icon_url, rt.icon_url as receive_asset_icon_url, rt.symbol as receive_asset_symbol, t.symbol as asset_symbol, rt.chain_id as receive_chain_id, t.chain_id as pay_chain_id, pc.name AS pay_chain_name, rc.name AS receive_chain_name
FROM swap_orders o
LEFT JOIN tokens t ON o.pay_asset_id = t.asset_id
LEFT JOIN tokens rt ON o.receive_asset_id = rt.asset_id
LEFT JOIN chains pc ON t.chain_id = pc.chain_id
LEFT JOIN chains rc ON rt.chain_id = rc.chain_id
WHERE o.order_id = :orderId
"""
)
fun getOrderById(orderId: String): Flow<SwapOrderItem?>

@Query("SELECT * FROM swap_orders WHERE state = 'pending'")
suspend fun getPendingOrders(): List<SwapOrder>

@Query(
"""
SELECT created_at FROM swap_orders ORDER BY created_at DESC LIMIT 1
"""
)
suspend fun lastOrderCreatedAt(): String?
}
4 changes: 4 additions & 0 deletions app/src/main/java/one/mixin/android/di/BaseDbModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,8 @@ internal object BaseDbModule {
@Provides
fun provideMarketCapRankDao(db: MixinDatabase) = db.marketCapRankDao()

@Singleton
@Provides
fun provideOrderDao(db: MixinDatabase) = db.orderDao()

}
5 changes: 5 additions & 0 deletions app/src/main/java/one/mixin/android/job/BaseJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import one.mixin.android.db.MessageHistoryDao
import one.mixin.android.db.MessageMentionDao
import one.mixin.android.db.MixinDatabase
import one.mixin.android.db.OffsetDao
import one.mixin.android.db.OrderDao
import one.mixin.android.db.OutputDao
import one.mixin.android.db.ParticipantDao
import one.mixin.android.db.ParticipantSessionDao
Expand Down Expand Up @@ -338,6 +339,10 @@ abstract class BaseJob(params: Params) : Job(params) {
@Transient
lateinit var inscriptionCollectionDao: InscriptionCollectionDao

@Inject
@Transient
lateinit var orderDao: OrderDao

@Inject
@Transient
lateinit var signalProtocol: SignalProtocol
Expand Down
29 changes: 29 additions & 0 deletions app/src/main/java/one/mixin/android/job/RefreshOrdersJob.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package one.mixin.android.job

import com.birbit.android.jobqueue.Params
import kotlinx.coroutines.runBlocking

class RefreshOrdersJob : BaseJob(Params(PRIORITY_BACKGROUND).singleInstanceBy(GROUP).requireNetwork().persist()) {
companion object {
private const val serialVersionUID = 2L
const val GROUP = "RefreshOrdersJob"
const val LIMIT = 20
}

override fun onRun(): Unit =
runBlocking {
val lastCreate = orderDao.lastOrderCreatedAt()
refreshOrders(lastCreate)
}

private suspend fun refreshOrders(offset: String?) {
val response = routeService.orders(limit = LIMIT, offset = offset)
if (response.isSuccess && response.data != null) {
orderDao.insertListSuspend(response.data!!)
if (response.data!!.size >= LIMIT) {
val lastCreate = response.data?.last()?.createdAt ?: return
refreshOrders(lastCreate)
}
}
}
}
31 changes: 31 additions & 0 deletions app/src/main/java/one/mixin/android/job/RefreshPendingOrdersJob.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package one.mixin.android.job

import com.birbit.android.jobqueue.Params
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

class RefreshPendingOrdersJob : BaseJob(Params(PRIORITY_BACKGROUND).singleInstanceBy(GROUP).requireNetwork().persist()) {
companion object {
private const val serialVersionUID = 2L
const val GROUP = "RefreshOrdersJob"
}

override fun onRun(): Unit =
runBlocking {
val pendingOrders = orderDao.getPendingOrders()
if (pendingOrders.isNotEmpty()) {
pendingOrders.forEach {
launch {
refreshPendingOrders(it.createdAt)
}
}
}
}

private suspend fun refreshPendingOrders(offset: String) {
val response = routeService.orders(limit = 1, offset = offset)
if (response.isSuccess && response.data != null) {
orderDao.insertListSuspend(response.data!!)
}
}
}
16 changes: 14 additions & 2 deletions app/src/main/java/one/mixin/android/repository/TokenRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import one.mixin.android.db.MarketCoinDao
import one.mixin.android.db.MarketDao
import one.mixin.android.db.MarketFavoredDao
import one.mixin.android.db.MixinDatabase
import one.mixin.android.db.OrderDao
import one.mixin.android.db.OutputDao
import one.mixin.android.db.RawTransactionDao
import one.mixin.android.db.SafeSnapshotDao
Expand Down Expand Up @@ -108,6 +109,8 @@ import one.mixin.android.vo.market.MarketCoin
import one.mixin.android.vo.market.MarketFavored
import one.mixin.android.vo.market.MarketItem
import one.mixin.android.vo.route.RoutePaymentRequest
import one.mixin.android.vo.route.SwapOrder
import one.mixin.android.vo.route.SwapOrderItem
import one.mixin.android.vo.safe.DepositEntry
import one.mixin.android.vo.safe.Output
import one.mixin.android.vo.safe.RawTransaction
Expand Down Expand Up @@ -159,6 +162,7 @@ class TokenRepository
private val marketCoinDao: MarketCoinDao,
private val marketFavoredDao: MarketFavoredDao,
private val alertDao: AlertDao,
private val orderDao: OrderDao,
private val jobManager: MixinJobManager,
private val safeBox: DataStore<SafeBox>,
) {
Expand Down Expand Up @@ -206,6 +210,7 @@ class TokenRepository
val output = outputDao.findOutputByHash(instantiationHash) ?: return null
if (assetItem == null) {
assetItem = syncAssetByKernel(output.asset)

}
if (assetItem != null && assetItem.chainId != assetItem.assetId && simpleAsset(assetItem.chainId) == null) {
val chain = syncAsset(assetItem.chainId)
Expand Down Expand Up @@ -752,6 +757,8 @@ class TokenRepository

suspend fun orders(): MixinResponse<List<RouteOrderResponse>> = routeService.payments()

fun swapOrders(): Flow<List<SwapOrderItem>> = orderDao.orders()

suspend fun createOrder(createSession: OrderRequest): MixinResponse<RouteOrderResponse> =
routeService.createOrder(createSession)

Expand Down Expand Up @@ -1080,8 +1087,10 @@ class TokenRepository

suspend fun findMarketItemByCoinId(coinId: String) = marketDao.findMarketItemByCoinId(coinId)

suspend fun checkMarketById(id: String): MarketItem? {
val marketItem = if (id.isUUID()) {
suspend fun checkMarketById(id: String, force: Boolean = false): MarketItem? {
val marketItem = if (force) {
null
} else if (id.isUUID()) {
findMarketItemByAssetId(id)
} else {
findMarketItemByCoinId(id)
Expand Down Expand Up @@ -1261,4 +1270,7 @@ class TokenRepository
}

suspend fun findChangeUsdByAssetId(assetId: String) = tokenDao.findChangeUsdByAssetId(assetId)

fun getOrderById(orderId: String): Flow<SwapOrderItem?> = orderDao.getOrderById(orderId)

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package one.mixin.android.ui.home.web3.swap

import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import android.view.ViewTreeObserver
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import timber.log.Timber

@Composable
Expand Down
Loading

0 comments on commit 61e3b9b

Please sign in to comment.