Skip to content

Commit

Permalink
Merge branch 'master' into feature/transfer-state-save
Browse files Browse the repository at this point in the history
  • Loading branch information
crossle authored Jan 23, 2025
2 parents 4aeb59a + c85773b commit e9a50f6
Show file tree
Hide file tree
Showing 61 changed files with 5,097 additions and 437 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: 0 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
Expand Down
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
80 changes: 39 additions & 41 deletions app/src/main/java/one/mixin/android/api/response/web3/SwapToken.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import one.mixin.android.api.response.solanaNativeTokenAssetKey
import one.mixin.android.api.response.wrappedSolTokenAssetKey
import one.mixin.android.extension.equalsIgnoreCase
import java.math.BigDecimal
import java.math.RoundingMode

Expand Down Expand Up @@ -91,61 +92,58 @@ interface Swappable : Parcelable {
fun getUnique(): String
}

fun List<SwapToken>.sortByKeywordAndBalance(keyword: String?): List<SwapToken> {
return sortedWith { a, b ->
when {
!keyword.isNullOrBlank() -> {
val aMatchLevel = getMatchLevel(a, keyword)
val bMatchLevel = getMatchLevel(b, keyword)
fun List<SwapToken>.sortByKeywordAndBalance(query: String?): List<SwapToken> {
return this.sortedWith(
Comparator { o1, o2 ->
if (o1 == null && o2 == null) return@Comparator 0
if (o1 == null) return@Comparator 1
if (o2 == null) return@Comparator -1

val equal2Keyword1 = o1.symbol.equalsIgnoreCase(query)
val equal2Keyword2 = o2.symbol.equalsIgnoreCase(query)
if (equal2Keyword1 && !equal2Keyword2) {
return@Comparator -1
} else if (!equal2Keyword1 && equal2Keyword2) {
return@Comparator 1
}

when {
aMatchLevel != bMatchLevel -> bMatchLevel.compareTo(aMatchLevel)
else -> a.symbol.compareTo(b.symbol)
val priceFiat1 = calculateTokenValue(o1)
val priceFiat2 = calculateTokenValue(o2)
val capitalization1 = priceFiat1 * runCatching { BigDecimal(o1.balance) }.getOrDefault(BigDecimal.ZERO)
val capitalization2 = priceFiat2 * runCatching { BigDecimal(o2.balance) }.getOrDefault(BigDecimal.ZERO)
if (capitalization1 != capitalization2) {
if (capitalization2 > capitalization1) {
return@Comparator 1
} else if (capitalization2 < capitalization1) {
return@Comparator -1
}
}

else -> {
compareByBalanceAndPrice(a, b)
if (priceFiat1 == BigDecimal.ZERO && priceFiat2 != BigDecimal.ZERO) {
return@Comparator 1
} else if (priceFiat1 != BigDecimal.ZERO && priceFiat2 == BigDecimal.ZERO) {
return@Comparator -1
}
}
}
}

private fun getMatchLevel(token: SwapToken, keyword: String): Int {
val symbolLower = token.symbol.lowercase()
val keywordLower = keyword.lowercase()

return when {
symbolLower == keywordLower -> 2
symbolLower.startsWith(keywordLower) -> 1
else -> 0
}
}

private fun compareByBalanceAndPrice(a: SwapToken, b: SwapToken): Int {
val aValue = calculateTokenValue(a)
val bValue = calculateTokenValue(b)

return when {
aValue != BigDecimal.ZERO || bValue != BigDecimal.ZERO -> bValue.compareTo(aValue)
else -> {
when {
!a.balance.isNullOrBlank() && !b.balance.isNullOrBlank() ->
b.balance!!.compareTo(a.balance!!)

!a.balance.isNullOrBlank() -> -1
!b.balance.isNullOrBlank() -> 1
else -> a.symbol.compareTo(b.symbol)
val hasIcon1 = o1.icon != defaultIcon
val hasIcon2 = o2.icon != defaultIcon
if (hasIcon1 && !hasIcon2) {
return@Comparator -1
} else if (!hasIcon1 && hasIcon2) {
return@Comparator 1
}

return@Comparator o1.name.compareTo(o2.name)
}
}
)
}

private const val defaultIcon = "https://images.mixin.one/yH_I5b0GiV2zDmvrXRyr3bK5xusjfy5q7FX3lw3mM2Ryx4Dfuj6Xcw8SHNRnDKm7ZVE3_LvpKlLdcLrlFQUBhds=s128"

private fun calculateTokenValue(token: SwapToken): BigDecimal {
if (token.balance.isNullOrBlank() || token.price.isNullOrBlank()) {
return BigDecimal.ZERO
}

return try {
BigDecimal(token.balance).multiply(BigDecimal(token.price))
} catch (e: Exception) {
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()

}
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ fun BigDecimal.currencyFormat(): String {

fun String?.isValidMao(): Boolean {
if (this.isNullOrBlank()) return false
val text = this.trimEnd('.')
val text = this.trimEnd('.').lowercase()
if (text.all { it.isDigit() }) return false
val regex = Regex("^[^\\sA-Z]{1,128}$")
return regex.matches(text)
Expand Down
19 changes: 17 additions & 2 deletions app/src/main/java/one/mixin/android/extension/UrlExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import one.mixin.android.vo.ShareCategory
import one.mixin.android.vo.User
import one.mixin.android.vo.generateConversationId
import one.mixin.android.vo.getShareCategory
import one.mixin.android.widget.gallery.MimeType
import timber.log.Timber

fun String.openAsUrlOrWeb(
Expand Down Expand Up @@ -169,7 +170,7 @@ User-agent: ${WebView(context).settings.userAgentString}
ConfirmBottomFragment.show(MixinApplication.appContext, supportFragmentManager, this)
} else if (isUserScheme() || isAppScheme()) {
checkUserOrApp(context, supportFragmentManager, scope)
} else if(isConversationScheme()) {
} else if (isConversationScheme()) {
checkConversation(context, scope) {
if (isMixinUrl() || isExternalScheme(context) || isExternalTransferUrl()) {
LinkBottomSheetDialogFragment.newInstance(this)
Expand Down Expand Up @@ -255,7 +256,7 @@ fun String.checkUserOrApp(
fun String.checkConversation(
context: Context,
scope: CoroutineScope,
elseAction: () -> Unit
elseAction: () -> Unit,
) {
val uri = Uri.parse(this)
val segments = uri.pathSegments
Expand Down Expand Up @@ -413,8 +414,22 @@ fun Uri.getCapturedImage(contentResolver: ContentResolver): Bitmap =
@Suppress("DEPRECATION")
MediaStore.Images.Media.getBitmap(contentResolver, this)
}

else -> {
val source = ImageDecoder.createSource(contentResolver, this)
ImageDecoder.decodeBitmap(source)
}
}

fun Uri.isVideo(context: Context) : Boolean{
val mimeType = context.contentResolver.getType(this)
return mimeType.equals(MimeType.MPEG.toString())
|| mimeType.equals(MimeType.MP4.toString())
|| mimeType.equals(MimeType.QUICKTIME.toString())
|| mimeType.equals(MimeType.THREEGPP.toString())
|| mimeType.equals(MimeType.THREEGPP2.toString())
|| mimeType.equals(MimeType.MKV.toString())
|| mimeType.equals(MimeType.WEBM.toString())
|| mimeType.equals(MimeType.TS.toString())
|| mimeType.equals(MimeType.AVI.toString())
}
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)
}
}
}
}
Loading

0 comments on commit e9a50f6

Please sign in to comment.