diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 048e2f771d..7c5c71355a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -275,7 +275,7 @@ dependencies { implementation(projects.features.tokenRecieve.api) implementation(projects.features.tokenRecieve.impl) implementation(projects.features.yieldSupply.api) - implementation(projects.features.yieldSupply.api) + implementation(projects.features.yieldSupply.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 6035f506dc..fb0bfb3588 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -34,9 +34,11 @@ import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.tangempay.components.TangemPayDetailsComponent +import com.tangem.features.tangempay.components.TangemPayOnboardingComponent import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent +import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent @@ -45,7 +47,6 @@ import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent import com.tangem.tap.features.welcome.component.WelcomeComponent import com.tangem.tap.routing.component.RoutingComponent.Child -import com.tangem.features.tangempay.components.TangemPayOnboardingComponent import dagger.hilt.android.scopes.ActivityScoped import javax.inject.Inject import com.tangem.features.walletconnect.components.WalletConnectEntryComponent as RedesignedWalletConnectComponent @@ -105,6 +106,7 @@ internal class ChildFactory @Inject constructor( private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory, private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory, + private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory, private val hotWalletFeatureToggles: HotWalletFeatureToggles, ) { @@ -600,6 +602,13 @@ internal class ChildFactory @Inject constructor( componentFactory = tangemPayOnboardingComponentFactory, ) } + is AppRoute.YieldSupplyPromo -> { + createComponentChild( + context = context, + params = YieldSupplyPromoComponent.Params(route.userWalletId, route.cryptoCurrency), + componentFactory = yieldSupplyPromoComponentFactory, + ) + } } } } \ No newline at end of file diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 3ca2c4c565..448f369ac0 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -382,4 +382,10 @@ sealed class AppRoute(val path: String) : Route { data class TangemPayOnboarding( val deeplink: String, ) : AppRoute(path = "/tangem_pay_onboarding/$deeplink") + + @Serializable + data class YieldSupplyPromo( + val userWalletId: UserWalletId, + val cryptoCurrency: CryptoCurrency, + ) : AppRoute(path = "/yield_supply_promo/${userWalletId.stringValue}/${cryptoCurrency.symbol}") } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt index 2e3d83710f..6128a71c4a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt @@ -10,4 +10,13 @@ interface ComposableBottomSheetComponent { @Composable fun BottomSheet() +} + +fun getEmptyComposableBottomSheetComponent() = EmptyComposableBottomSheetComponent + +object EmptyComposableBottomSheetComponent : ComposableBottomSheetComponent { + override fun dismiss() {} + + @Composable + override fun BottomSheet() {} } \ No newline at end of file diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt new file mode 100644 index 0000000000..0516efb037 --- /dev/null +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.yield.supply.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId + +interface YieldSupplyPromoComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/yield-supply/impl/build.gradle.kts b/features/yield-supply/impl/build.gradle.kts index 14dbb06d31..95021510f8 100644 --- a/features/yield-supply/impl/build.gradle.kts +++ b/features/yield-supply/impl/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(projects.core.configToggles) implementation(projects.core.decompose) implementation(projects.core.ui) + implementation(projects.core.navigation) /** Common */ implementation(projects.common.ui) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/DefaultYieldSupplyPromoComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/DefaultYieldSupplyPromoComponent.kt new file mode 100644 index 0000000000..7e9414c4a1 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/DefaultYieldSupplyPromoComponent.kt @@ -0,0 +1,56 @@ +package com.tangem.features.yield.supply.impl.promo + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.decompose.getEmptyComposableBottomSheetComponent +import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent +import com.tangem.features.yield.supply.impl.promo.model.YieldSupplyPromoModel +import com.tangem.features.yield.supply.impl.promo.ui.YieldSupplyPromoContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultYieldSupplyPromoComponent @AssistedInject constructor( + @Assisted private val appComponentContext: AppComponentContext, + @Assisted private val params: YieldSupplyPromoComponent.Params, +) : YieldSupplyPromoComponent, AppComponentContext by appComponentContext { + + private val model: YieldSupplyPromoModel = getOrCreateModel(params = params) + + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = { _, _ -> bottomSheetChild() }, + ) + + @Composable + override fun Content(modifier: Modifier) { + val state = model.uiState + val bottomSheet by bottomSheetSlot.subscribeAsState() + + YieldSupplyPromoContent( + yieldSupplyPromoUM = state, + clickIntents = model, + modifier = modifier, + ) + + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild(): ComposableBottomSheetComponent = getEmptyComposableBottomSheetComponent() + + @AssistedFactory + interface Factory : YieldSupplyPromoComponent.Factory { + override fun create( + context: AppComponentContext, + params: YieldSupplyPromoComponent.Params, + ): DefaultYieldSupplyPromoComponent + } +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/YieldSupplyPromoConfig.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/YieldSupplyPromoConfig.kt new file mode 100644 index 0000000000..7a87ea9946 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/YieldSupplyPromoConfig.kt @@ -0,0 +1,6 @@ +package com.tangem.features.yield.supply.impl.promo + +internal enum class YieldSupplyPromoConfig { + Apy, + Action, +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/di/YieldSupplyPromoBindsModule.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/di/YieldSupplyPromoBindsModule.kt new file mode 100644 index 0000000000..95e0a79cc6 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/di/YieldSupplyPromoBindsModule.kt @@ -0,0 +1,34 @@ +package com.tangem.features.yield.supply.impl.promo.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.yield.supply.impl.promo.DefaultYieldSupplyPromoComponent +import com.tangem.features.yield.supply.impl.promo.model.YieldSupplyPromoModel +import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface YieldSupplyPromoBindsModule { + @Binds + @Singleton + fun provideYieldSupplyPromoComponentFactory( + impl: DefaultYieldSupplyPromoComponent.Factory, + ): YieldSupplyPromoComponent.Factory +} + +@Module +@InstallIn(ModelComponent::class) +internal interface YieldSupplyPromoModelModule { + + @Binds + @IntoMap + @ClassKey(YieldSupplyPromoModel::class) + fun provideYieldSupplyPromoModel(impl: YieldSupplyPromoModel): Model +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt new file mode 100644 index 0000000000..51eefdad41 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt @@ -0,0 +1,9 @@ +package com.tangem.features.yield.supply.impl.promo.entity + +import com.tangem.core.ui.extensions.TextReference + +data class YieldSupplyPromoUM( + val tosLink: String, + val policyLink: String, + val title: TextReference, +) \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoClickIntents.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoClickIntents.kt new file mode 100644 index 0000000000..0bf70fa048 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoClickIntents.kt @@ -0,0 +1,14 @@ +package com.tangem.features.yield.supply.impl.promo.model + +internal interface YieldSupplyPromoClickIntents { + + fun onBackClick() + + fun onApyInfoClick() + + fun onHowItWorksClick() + + fun onStartEarningClick() + + fun onUrlClick(url: String) +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt new file mode 100644 index 0000000000..9dbc5ecdd9 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt @@ -0,0 +1,51 @@ +package com.tangem.features.yield.supply.impl.promo.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.features.yield.supply.impl.R +import com.tangem.features.yield.supply.impl.promo.YieldSupplyPromoConfig +import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import javax.inject.Inject + +@ModelScoped +internal class YieldSupplyPromoModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val urlOpener: UrlOpener, + private val appRouter: AppRouter, +) : Model(), YieldSupplyPromoClickIntents { + + val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM( + tosLink = "https://tangem.com/terms-of-service/", // TODO replace with real link + policyLink = "https://tangem.com/privacy-policy/", // TODO replace with real link + title = resourceReference(R.string.yield_module_promo_screen_title, wrappedList("5.3")), + ) + + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + + override fun onBackClick() { + appRouter.pop() + } + + override fun onApyInfoClick() { + bottomSheetNavigation.activate(YieldSupplyPromoConfig.Apy) + } + + override fun onUrlClick(url: String) { + urlOpener.openUrl(url) + } + + override fun onHowItWorksClick() { + urlOpener.openUrl("https://tangem.com/") // TODO replace with real link + } + + override fun onStartEarningClick() { + bottomSheetNavigation.activate(YieldSupplyPromoConfig.Action) + } +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt new file mode 100644 index 0000000000..5839f6fb28 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt @@ -0,0 +1,239 @@ +package com.tangem.features.yield.supply.impl.promo.ui + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withLink +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.label.Label +import com.tangem.core.ui.components.label.entity.LabelStyle +import com.tangem.core.ui.components.label.entity.LabelUM +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.yield.supply.impl.R +import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM +import com.tangem.features.yield.supply.impl.promo.model.YieldSupplyPromoClickIntents +import com.tangem.utils.StringsSigns + +@Suppress("MagicNumber") +@Composable +internal fun YieldSupplyPromoContent( + yieldSupplyPromoUM: YieldSupplyPromoUM, + clickIntents: YieldSupplyPromoClickIntents, + modifier: Modifier = Modifier, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .background(color = TangemTheme.colors.background.tertiary) + .fillMaxWidth() + .imePadding() + .systemBarsPadding() + .padding(horizontal = 16.dp), + ) { + YieldStatusAppBar( + onBackClick = clickIntents::onBackClick, + onHowItWorksClick = clickIntents::onHowItWorksClick, + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 20.dp), + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + modifier = Modifier + .background(TangemTheme.colors.icon.accent.copy(0.1f), CircleShape) + .padding(12.dp) + .size(32.dp), + ) + SpacerH(20.dp) + Text( + text = yieldSupplyPromoUM.title.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + SpacerH8() + Label( + state = LabelUM( + text = resourceReference(R.string.yield_module_promo_screen_variable_rate_info), + style = LabelStyle.REGULAR, + icon = R.drawable.ic_information_24, + onIconClick = clickIntents::onApyInfoClick, + ), + ) + SpacerH32() + PromoItems() + } + SpacerHMax() + YieldSupplyTosText( + tosLink = yieldSupplyPromoUM.tosLink, + policyLink = yieldSupplyPromoUM.policyLink, + onClick = clickIntents::onUrlClick, + ) + PrimaryButton( + text = stringResourceSafe(R.string.yield_module_start_earning), + onClick = clickIntents::onStartEarningClick, + modifier = Modifier.fillMaxWidth(), + ) + SpacerH8() + } +} + +@Composable +private fun YieldStatusAppBar(onBackClick: () -> Unit, onHowItWorksClick: () -> Unit) { + Row( + modifier = Modifier.padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_back_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + modifier = Modifier.clickable(onClick = onBackClick), + ) + SpacerWMax() + Text( + text = stringResourceSafe(R.string.yield_module_promo_screen_how_it_works_button_title), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.clickable(onClick = onHowItWorksClick), + ) + } +} + +@Composable +private fun PromoItems() { + PromoItem( + icon = R.drawable.ic_flash_new_24, + title = resourceReference(R.string.yield_module_promo_screen_cash_out_title), + subtitle = resourceReference(R.string.yield_module_promo_screen_cash_out_subtitle), + ) + SpacerH24() + PromoItem( + icon = R.drawable.ic_repeat_24, + title = resourceReference(R.string.yield_module_promo_screen_auto_balance_title), + subtitle = resourceReference(R.string.yield_module_promo_screen_auto_balance_subtitle), + ) + SpacerH24() + PromoItem( + icon = R.drawable.ic_security_check_24, + title = resourceReference(R.string.yield_module_promo_screen_self_custodial_title), + subtitle = resourceReference(R.string.yield_module_promo_screen_self_custodial_subtitle), + ) +} + +@Composable +private fun PromoItem(@DrawableRes icon: Int, title: TextReference, subtitle: TextReference) { + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp), + ) { + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + Column(verticalArrangement = Arrangement.spacedBy(3.dp)) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = subtitle.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } + } +} + +@Composable +private fun YieldSupplyTosText(tosLink: String, policyLink: String, onClick: (String) -> Unit) { + val tosTitle = stringResourceSafe(R.string.common_terms_of_use) + val policyTitle = stringResourceSafe(R.string.common_privacy_policy) + val fullString = stringResourceSafe(id = R.string.yield_module_promo_screen_terms_disclaimer, tosTitle, policyTitle) + val tosIndex = fullString.indexOf(tosTitle) + val policyIndex = fullString.indexOf(policyTitle) + + Text( + text = buildAnnotatedString { + append(StringsSigns.POINT_SIGN) + appendSpace() + append(fullString.substring(0, tosIndex)) + withLink( + link = LinkAnnotation.Clickable( + tag = "TOS_TAG", + linkInteractionListener = { onClick(tosLink) }, + ), + block = { + appendColored( + text = fullString.substring(tosIndex, tosIndex + tosTitle.length), + color = TangemTheme.colors.text.accent, + ) + }, + ) + append(fullString.substring(tosIndex + tosTitle.length, policyIndex)) + withLink( + link = LinkAnnotation.Clickable( + tag = "POLICY_TAG", + linkInteractionListener = { onClick(policyLink) }, + ), + block = { + appendColored( + text = fullString.substring(policyIndex, policyIndex + policyTitle.length), + color = TangemTheme.colors.text.accent, + ) + }, + ) + }, + textAlign = TextAlign.Center, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360, heightDp = 724) +@Preview(showBackground = true, widthDp = 360, heightDp = 724, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun YieldSupplyPromoContent_Preview() { + TangemThemePreview { + YieldSupplyPromoContent( + yieldSupplyPromoUM = YieldSupplyPromoUM( + tosLink = "https://tangem.com/terms-of-service/", + policyLink = "https://tangem.com/privacy-policy/", + title = resourceReference(R.string.yield_module_promo_screen_title, wrappedList("5.3")), + ), + clickIntents = object : YieldSupplyPromoClickIntents { + override fun onBackClick() {} + override fun onApyInfoClick() {} + override fun onHowItWorksClick() {} + override fun onStartEarningClick() {} + override fun onUrlClick(url: String) {} + }, + ) + } +} + +// endregion \ No newline at end of file