Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cross chain swaps #1657

Open
wants to merge 76 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
1733003
Xyk swaps
valentunn Jun 3, 2024
33d9480
Some logging
valentunn Jun 3, 2024
1a04be9
Code style
valentunn Jun 3, 2024
ced775c
WIP
valentunn Jun 11, 2024
2c5b30d
WIP SwapService interface finalized
valentunn Jun 12, 2024
cfb6623
Hydra dx rewritten
valentunn Jul 3, 2024
11ac98c
Make the build work
valentunn Sep 17, 2024
bae884e
Enable logging back
valentunn Sep 17, 2024
7b6a698
Merge branch 'develop' into feature/cross_chain_swaps
valentunn Oct 7, 2024
9de6ccd
Fix merge conflicts with dev
valentunn Oct 7, 2024
523e770
Optimized method for swap availability
valentunn Oct 8, 2024
281124d
Optimize Dijkstra
valentunn Oct 8, 2024
6a65d02
Small tweaks
valentunn Oct 8, 2024
f877f3a
Cross chain quoting WIP
valentunn Oct 8, 2024
4a9fc75
Multi-chain swap fees mechanism (domain)
valentunn Oct 9, 2024
8061115
Cross chain fees work
valentunn Oct 11, 2024
1205c0d
Determine ability to pay initial submission fee based on extrinsic se…
valentunn Oct 11, 2024
bcfb482
Filter out directions that do not support fee payment
valentunn Oct 16, 2024
db1e374
Allow edges to ignore fee requirement based on the predecessor
valentunn Oct 16, 2024
fbca0f7
Filter out cross chain directions with delivery fees
valentunn Oct 16, 2024
0411604
Submit hydration extrinsic and better fees
valentunn Oct 21, 2024
31ae3a8
Improve logging
valentunn Oct 21, 2024
59b9048
Improve logging
valentunn Oct 21, 2024
295672e
Cross chain transfers submission
valentunn Oct 21, 2024
adc0a8c
Change batch type to fix tx inclusion on hydra
valentunn Oct 22, 2024
394dfcf
Change terminology in SubmissionOrigin
valentunn Oct 22, 2024
ae04547
Fixes
valentunn Oct 22, 2024
8fc2642
Increase timeout
valentunn Oct 22, 2024
1ab7257
Add buffer to hydra fee conversion
valentunn Oct 22, 2024
6ab5c46
Finish asset conversion swaps and assets cross chain deposits
valentunn Oct 23, 2024
7a32fec
Roughly estimate fees for quotes for choosing best candidate
valentunn Oct 23, 2024
5f78776
Fixes
valentunn Oct 23, 2024
4cab1cf
Only use BUY for the first segment and use SELL for the rest. Use new…
valentunn Oct 23, 2024
9af2869
Filter out non-sufficient assets
valentunn Oct 29, 2024
17a9c61
Remove GenericFee and DecimalFee
valentunn Oct 29, 2024
e5e9f5a
Fix
valentunn Oct 29, 2024
2282899
Fees refactoring
valentunn Oct 31, 2024
2b1db4f
Fix cornercases on swap and send
valentunn Oct 31, 2024
8dc2ab3
Warm up fee visitor to improve asset to receive loading
valentunn Oct 31, 2024
16d35a6
Skip edges user doesn't have account on destination for
valentunn Oct 31, 2024
22b9483
Merge branch 'develop' into feature/cross_chain_swaps
valentunn Nov 5, 2024
3af944f
Fix merge conflicts
valentunn Nov 5, 2024
131147f
Route short view
valentunn Nov 6, 2024
c227525
Fix swap filtering issues
valentunn Nov 6, 2024
63ed9ea
Swap route details
valentunn Nov 7, 2024
5f6c381
Swap fee details
valentunn Nov 8, 2024
8e2f4fc
Estimate execution time
valentunn Nov 8, 2024
97b30b6
Merge branch 'develop' into feature/cross_chain_swaps
valentunn Nov 8, 2024
3567705
Fixes
valentunn Nov 8, 2024
7e09360
Swap Execution WIP
valentunn Nov 11, 2024
dc6d91d
Fix - outdated asset group info was used to handle clicks
valentunn Nov 11, 2024
7af2a3a
Optimizations
valentunn Nov 11, 2024
b03b5cd
Merge branch 'develop' into feature/cross_chain_swaps
valentunn Nov 12, 2024
5e43a5d
Execution screen ui ready
valentunn Nov 12, 2024
ae19c4a
Fixes
valentunn Nov 12, 2024
a416d2e
Validations
valentunn Nov 15, 2024
a639aea
Merge branch 'develop' into feature/cross_chain_swaps
valentunn Nov 20, 2024
02de9df
Fixes
valentunn Nov 20, 2024
c3f75c6
Do not crash if fee payment capability computation failed
valentunn Nov 20, 2024
af31592
Fixes
valentunn Nov 20, 2024
8c3fa5a
Navigation fixes and swap execution improvements WIP
valentunn Nov 21, 2024
39e3066
Fixes
valentunn Nov 21, 2024
0e8f961
Fixes
valentunn Nov 21, 2024
0a1cd24
Swap execution animation
antonijzelinskij Nov 22, 2024
5cd0a42
Run ktlint
antonijzelinskij Nov 22, 2024
8b46c40
Merge pull request #1727 from novasamatech/feature/swap-execution-ani…
valentunn Nov 22, 2024
0279ce8
Rename network fee to total fee for swap
valentunn Nov 22, 2024
fa558e2
Fix - clicking execution time collapses swap details
valentunn Nov 22, 2024
c82534f
Refactor max button logic
valentunn Nov 22, 2024
8f0e1dc
Fix route step layout
valentunn Nov 22, 2024
84c56d6
Fix execution states
valentunn Nov 22, 2024
279ff99
Add detection of xcm arrivals in ParachainSystem.set_validation_data …
valentunn Nov 27, 2024
4ce61aa
Fix - asset out sufficiency constraint did not apply when asset in is…
valentunn Nov 27, 2024
551b551
Fix fee switching
valentunn Nov 27, 2024
3c50e74
Fix - sufficiency deduction worked in reverse; automatic fee switch d…
valentunn Nov 27, 2024
c2074d4
Add on Finality rate limit error code
valentunn Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@ app/*.apk
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/8.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/9.json

google-services.json
/bindings
google-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.Percent
import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi
import io.novafoundation.nova.feature_swap_api.domain.model.SwapExecuteArgs
import io.novafoundation.nova.feature_swap_api.domain.model.SwapFeeArgs
import io.novafoundation.nova.feature_swap_api.domain.model.SwapLimit
import io.novafoundation.nova.feature_swap_api.domain.model.SwapQuote
import io.novafoundation.nova.feature_swap_api.domain.model.SwapQuoteArgs
import io.novafoundation.nova.feature_swap_core.domain.model.SwapQuoteException
import io.novafoundation.nova.feature_swap_core_api.data.primitive.errors.SwapQuoteException
import io.novafoundation.nova.feature_swap_api.domain.model.swapRate
import io.novafoundation.nova.feature_swap_core.domain.model.QuotePath
import io.novafoundation.nova.feature_swap_core.domain.model.SwapDirection
import io.novafoundation.nova.feature_swap_core_api.data.primitive.model.SwapDirection
import io.novafoundation.nova.feature_swap_impl.di.SwapFeatureComponent
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
Expand Down Expand Up @@ -80,7 +79,7 @@ class SwapServiceIntegrationTest : BaseIntegrationTest() {
val wnd = westmint.wnd()
val siri = westmint.siri()

val swapArgs = SwapExecuteArgs(
val swapArgs = SwapFeeArgs(
assetIn = wnd,
assetOut = siri,
swapLimit = SwapLimit.SpecifiedIn(
Expand All @@ -104,7 +103,7 @@ class SwapServiceIntegrationTest : BaseIntegrationTest() {
val wnd = westmint.wnd()
val siri = westmint.siri()

val swapArgs = SwapExecuteArgs(
val swapArgs = SwapFeeArgs(
assetIn = siri,
assetOut = wnd,
swapLimit = SwapLimit.SpecifiedIn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import io.novafoundation.nova.feature_settings_impl.di.SettingsFeatureHolder
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureHolder
import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi
import io.novafoundation.nova.feature_swap_core.di.SwapCoreApi
import io.novafoundation.nova.feature_swap_core.di.SwapCoreHolder
import io.novafoundation.nova.feature_swap_core_api.di.SwapCoreApi
import io.novafoundation.nova.feature_swap_impl.di.SwapFeatureHolder
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_versions_impl.di.VersionsFeatureHolder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,14 +399,14 @@ class Navigator(
navController?.navigate(R.id.action_selectAssetSwapFlowFragment_to_swapFlowNetworkFragment, NetworkSwapFlowFragment.createPayload(payload))
}

override fun openBuyNetworks(payload: NetworkFlowPayload) {
navController?.navigate(R.id.action_buyFlow_to_buyFlowNetwork, NetworkFlowFragment.createPayload(payload))
}

override fun returnToMainSwapScreen() {
navController?.navigate(R.id.action_return_to_swap_settings)
}

override fun openBuyNetworks(payload: NetworkFlowPayload) {
navController?.navigate(R.id.action_buyFlow_to_buyFlowNetwork, NetworkFlowFragment.createPayload(payload))
}

override fun openSwapFlow() {
val payload = SwapFlowPayload.InitialSelecting
navController?.navigate(R.id.action_mainFragment_to_swapFlow, AssetSwapFlowFragment.getBundle(payload))
Expand All @@ -416,6 +416,10 @@ class Navigator(
navController?.navigate(R.id.action_open_swapSetupAmount, SwapMainSettingsFragment.getBundle(swapSettingsPayload))
}

override fun finishSelectAndOpenSwapSetupAmount(swapSettingsPayload: SwapSettingsPayload) {
navController?.navigate(R.id.action_finish_and_open_swap_settings, SwapMainSettingsFragment.getBundle(swapSettingsPayload))
}

override fun openNfts() {
navController?.navigate(R.id.action_mainFragment_to_nfts_nav_graph)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,39 @@ import io.novafoundation.nova.app.R
import io.novafoundation.nova.app.root.navigation.BaseNavigator
import io.novafoundation.nova.app.root.navigation.NavigationHolder
import io.novafoundation.nova.app.root.navigation.Navigator
import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayload
import io.novafoundation.nova.feature_assets.presentation.balance.detail.BalanceDetailFragment
import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayload
import io.novafoundation.nova.feature_assets.presentation.swap.asset.AssetSwapFlowFragment
import io.novafoundation.nova.feature_assets.presentation.swap.asset.SwapFlowPayload
import io.novafoundation.nova.feature_swap_api.presentation.model.SwapSettingsPayload
import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter
import io.novafoundation.nova.feature_swap_impl.presentation.confirmation.SwapConfirmationFragment
import io.novafoundation.nova.feature_swap_impl.presentation.confirmation.payload.SwapConfirmationPayload
import io.novafoundation.nova.feature_swap_impl.presentation.main.SwapMainSettingsFragment
import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload

class SwapNavigator(
private val navigationHolder: NavigationHolder,
private val commonDelegate: Navigator
) : BaseNavigator(navigationHolder), SwapRouter {

override fun openSwapConfirmation(payload: SwapConfirmationPayload) {
val bundle = SwapConfirmationFragment.getBundle(payload)
navigationHolder.navController?.navigate(R.id.action_swapMainSettingsFragment_to_swapConfirmationFragment, bundle)
}
override fun openSwapConfirmation() = performNavigation(R.id.action_swapMainSettingsFragment_to_swapConfirmationFragment)

override fun openSwapRoute() = performNavigation(R.id.action_open_swapRouteFragment)

override fun openSwapFee() = performNavigation(R.id.action_open_swapFeeFragment)

override fun openSwapExecution() = performNavigation(R.id.action_swapConfirmationFragment_to_swapExecutionFragment)

override fun openSwapOptions() {
navigationHolder.navController?.navigate(R.id.action_swapMainSettingsFragment_to_swapOptionsFragment)
}

override fun openRetrySwap(payload: SwapSettingsPayload) = performNavigation(
actionId = R.id.action_swapExecutionFragment_to_swapSettingsFragment,
args = SwapMainSettingsFragment.getBundle(payload)
)

override fun openBalanceDetails(assetPayload: AssetPayload) {
navigationHolder.navController?.navigate(R.id.action_swapConfirmationFragment_to_assetDetails, BalanceDetailFragment.getBundle(assetPayload))
navigationHolder.navController?.navigate(R.id.action_swapExecutionFragment_to_assetDetails, BalanceDetailFragment.getBundle(assetPayload))
}

override fun selectAssetIn(selectedAsset: AssetPayload?) {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/navigation/main_nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@

</fragment>


<fragment
android:id="@+id/sendFlowNetworkFragment"
android:name="io.novafoundation.nova.feature_assets.presentation.send.flow.network.NetworkSendFlowFragment"
Expand Down Expand Up @@ -1004,6 +1005,8 @@

<include app:graph="@navigation/start_swap_nav_graph" />

<include app:graph="@navigation/select_swap_token_nav_graph" />

<include app:graph="@navigation/manage_tokens_graph" />

<include app:graph="@navigation/sign_parity_signer_graph" />
Expand Down
18 changes: 14 additions & 4 deletions app/src/main/res/navigation/select_swap_token_nav_graph.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="@id/selectAssetSwapFlowFragment"
android:id="@+id/select_swap_token_nav_graph">
android:id="@+id/select_swap_token_nav_graph"
app:startDestination="@id/selectAssetSwapFlowFragment">

<fragment
android:id="@+id/selectAssetSwapFlowFragment"
Expand All @@ -25,9 +25,19 @@

<action
android:id="@+id/action_return_to_swap_settings"
app:popUpTo="@id/swapSettingsFragment"
app:enterAnim="@anim/fragment_close_enter"
app:exitAnim="@anim/fragment_close_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit" />
app:popExitAnim="@anim/fragment_open_exit"
app:popUpTo="@id/swapSettingsFragment" />

<action
android:id="@+id/action_finish_and_open_swap_settings"
app:destination="@+id/start_swap_nav_graph"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpToInclusive="true"
app:popUpTo="@id/select_swap_token_nav_graph" />
</navigation>
60 changes: 51 additions & 9 deletions app/src/main/res/navigation/start_swap_nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,19 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />

</fragment>

<fragment
android:id="@+id/swapConfirmationFragment"
android:name="io.novafoundation.nova.feature_swap_impl.presentation.confirmation.SwapConfirmationFragment"
android:label="SwapConfirmationFragment"
tools:layout="@layout/fragment_swap_confirmation_settings">
tools:layout="@layout/fragment_swap_confirmation">

<action
android:id="@+id/action_swapConfirmationFragment_to_assetDetails"
app:destination="@id/balanceDetailFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@id/mainFragment" />
android:id="@+id/action_swapConfirmationFragment_to_swapExecutionFragment"
app:popUpTo="@id/swapConfirmationFragment"
app:popUpToInclusive="true"
app:destination="@id/swapExecutionFragment" />

</fragment>

Expand All @@ -60,4 +56,50 @@

<include app:graph="@navigation/select_swap_token_nav_graph" />

<fragment
android:id="@+id/swapRouteFragment"
android:name="io.novafoundation.nova.feature_swap_impl.presentation.route.SwapRouteFragment"
android:label="SwapRouteFragment" />

<action
android:id="@+id/action_open_swapRouteFragment"
app:destination="@id/swapRouteFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />

<dialog
android:id="@+id/swapFeeFragment"
android:name="io.novafoundation.nova.feature_swap_impl.presentation.fee.SwapFeeFragment"
android:label="SwapFeeFragment" />

<action
android:id="@+id/action_open_swapFeeFragment"
app:destination="@id/swapFeeFragment" />

<fragment
android:id="@+id/swapExecutionFragment"
android:name="io.novafoundation.nova.feature_swap_impl.presentation.execution.SwapExecutionFragment"
android:label="SwapExecutionFragment"
tools:layout="@layout/fragment_swap_execution">

<action
android:id="@+id/action_swapExecutionFragment_to_assetDetails"
app:destination="@id/balanceDetailFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@id/mainFragment" />

<action
android:id="@+id/action_swapExecutionFragment_to_swapSettingsFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@+id/start_swap_nav_graph"
app:destination="@id/swapSettingsFragment" />
</fragment>
</navigation>
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ public static native String calculate_pool_trade_fee(
String feeNumerator,
String feeDenominator
);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package io.novafoundation.nova.common.data.network.runtime.binding

import io.novafoundation.nova.common.utils.system
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericEvent
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import java.math.BigInteger

class EventRecord(val phase: Phase, val event: GenericEvent.Instance)
Expand All @@ -20,14 +16,17 @@ sealed class Phase {
object Initialization : Phase()
}

@HelperBinding
fun bindEventRecord(dynamicInstance: Any?): EventRecord {
fun bindEventRecords(decoded: Any?): List<EventRecord> {
return bindList(decoded, ::bindEventRecord)
}

private fun bindEventRecord(dynamicInstance: Any?): EventRecord {
requireType<Struct.Instance>(dynamicInstance)

val phaseDynamic = dynamicInstance.getTyped<DictEnum.Entry<*>>("phase")

val phase = when (phaseDynamic.name) {
"ApplyExtrinsic" -> Phase.ApplyExtrinsic(phaseDynamic.value.cast())
"ApplyExtrinsic" -> Phase.ApplyExtrinsic(bindNumber(phaseDynamic.value))
"Finalization" -> Phase.Finalization
"Initialization" -> Phase.Initialization
else -> incompatible()
Expand All @@ -37,18 +36,3 @@ fun bindEventRecord(dynamicInstance: Any?): EventRecord {

return EventRecord(phase, dynamicEvent)
}

@UseCaseBinding
fun bindEventRecords(
scale: String,
runtime: RuntimeSnapshot,
): List<EventRecord> {
val returnType = runtime.metadata.system().storage("Events").type.value ?: incompatible()

val dynamicInstance = returnType.fromHex(runtime, scale)
requireType<List<*>>(dynamicInstance)

return dynamicInstance.mapNotNull { dynamicEventRecord ->
bindOrNull { bindEventRecord(dynamicEventRecord) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class RealAssetsIconModeRepository(
}

override fun getIconMode(): AssetIconMode {
return ASSET_ICON_MODE_DEFAULT
return preferences.getString(PREFS_ASSETS_ICON_MODE)?.fromPrefsValue() ?: ASSET_ICON_MODE_DEFAULT
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ val <T> ExtendedLoadingState<T>.dataOrNull: T?
else -> null
}

@get:JvmName("isErrorProp")
val ExtendedLoadingState<*>.isError: Boolean
get() = this is ExtendedLoadingState.Error

fun <T> ExtendedLoadingState<T?>.loadedAndEmpty(): Boolean = when (this) {
is ExtendedLoadingState.Loaded -> data == null
else -> false
Expand All @@ -55,6 +59,10 @@ fun ExtendedLoadingState<*>.isLoading(): Boolean {
return this is ExtendedLoadingState.Loading
}

@get:JvmName("isLoadingProp")
val ExtendedLoadingState<*>.isLoading: Boolean
get() = isLoading()

fun ExtendedLoadingState<*>.isError(): Boolean {
return this is ExtendedLoadingState.Error
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ fun BaseFragmentMixin<*>.observeRetries(
retriable: Retriable,
context: Context = fragment.requireContext(),
) {
retriable.retryEvent.observeEvent {
retryDialog(
context = context,
onRetry = it.onRetry,
onCancel = it.onCancel
) {
setTitle(it.title)
setMessage(it.message)
with(retriable) {
retryEvent.observeEvent {
retryDialog(
context = context,
onRetry = it.onRetry,
onCancel = it.onCancel
) {
setTitle(it.title)
setMessage(it.message)
}
}
}
}
Expand Down
Loading
Loading