From 840c646bb73be711f5dce50a605486602ef5675e Mon Sep 17 00:00:00 2001 From: MayconCardoso Date: Wed, 14 Oct 2020 16:25:24 +0100 Subject: [PATCH 1/8] - Split navigation flow. --- .../navigation/AppNavigatorHandler.kt | 83 ++++++++++--------- .../interaction/GetFinalBalanceCase.kt | 25 +----- .../interaction/GetFinalDailyBalanceCase.kt | 26 +----- .../strategies/ComputeBalanceStrategy.kt | 30 ++++++- .../strategies/FilterStockListStrategy.kt | 5 +- .../stock_share/StockShareNavigator.kt | 1 + .../StockShareEditPositionCommand.kt | 4 + .../StockShareEditPositionFragment.kt | 6 ++ .../StockShareEditPositionInteraction.kt | 1 + .../StockShareEditPositionViewModel.kt | 5 ++ .../main/res/menu/stock_share_delete_menu.xml | 6 ++ .../src/main/res/values/strings.xml | 1 + .../src/main/res/drawable/ic_split.xml | 10 +++ 13 files changed, 113 insertions(+), 90 deletions(-) create mode 100644 libraries/library-design-system/src/main/res/drawable/ic_split.xml diff --git a/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt b/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt index fc4da89..4b7cc42 100644 --- a/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt +++ b/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt @@ -11,51 +11,56 @@ import com.mctech.stocktradetracking.feature.timeline_balance.TimelineBalanceNav import com.mctech.stocktradetracking.feature.timeline_balance.list_period.TimelineBalanceListFragmentDirections object AppNavigatorHandler : - StockShareNavigator, - TimelineBalanceNavigator, - StockShareFilterNavigator -{ - private var navController: NavController? = null - - fun bind(navController: NavController) { - this.navController = navController - } + StockShareNavigator, + TimelineBalanceNavigator, + StockShareFilterNavigator { + private var navController: NavController? = null - fun unbind() { - navController = null - } + fun bind(navController: NavController) { + this.navController = navController + } - override fun navigateBack() { - navController?.navigateUp() - } + fun unbind() { + navController = null + } - override fun fromStockListToEditPosition(stockShare: StockShare) { - val bundle = Bundle().apply { - putSerializable("stockShare", stockShare) - } - navController?.navigate( - R.id.action_global_stockShareEditPriceFragment, - bundle - ) - } + override fun navigateBack() { + navController?.navigateUp() + } - override fun fromStockListToBuyPosition() { - navController?.navigate(R.id.action_global_stockShareBuyFragment) + override fun fromStockListToEditPosition(stockShare: StockShare) { + val bundle = Bundle().apply { + putSerializable("stockShare", stockShare) } + navController?.navigate( + R.id.action_global_stockShareEditPriceFragment, + bundle + ) + } - override fun fromStockListToFilter() { - navController?.navigate(R.id.action_global_stockShareFilterFragment) - } + override fun fromEditToSplitPosition(stockShare: StockShare) { + navController?.navigateUp() + } - override fun fromTimelineToEditPeriod(currentPeriod: TimelineBalance) { - val destination = TimelineBalanceListFragmentDirections.actionTimelineBalanceFragmentToTimelineBalanceEditPeriodFragment( - currentPeriod - ) - navController?.navigate(destination) - } + override fun fromStockListToBuyPosition() { + navController?.navigate(R.id.action_global_stockShareBuyFragment) + } - override fun fromTimelineToOpenPeriod() { - val destination = TimelineBalanceListFragmentDirections.actionTimelineBalanceFragmentToTimelineBalanceAddPeriodFragment() - navController?.navigate(destination) - } + override fun fromStockListToFilter() { + navController?.navigate(R.id.action_global_stockShareFilterFragment) + } + + override fun fromTimelineToEditPeriod(currentPeriod: TimelineBalance) { + val destination = + TimelineBalanceListFragmentDirections.actionTimelineBalanceFragmentToTimelineBalanceEditPeriodFragment( + currentPeriod + ) + navController?.navigate(destination) + } + + override fun fromTimelineToOpenPeriod() { + val destination = + TimelineBalanceListFragmentDirections.actionTimelineBalanceFragmentToTimelineBalanceAddPeriodFragment() + navController?.navigate(destination) + } } \ No newline at end of file diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalBalanceCase.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalBalanceCase.kt index 961c06e..f80de37 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalBalanceCase.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalBalanceCase.kt @@ -1,29 +1,8 @@ package com.mctech.stocktradetracking.domain.stock_share.interaction -import com.mctech.stocktradetracking.domain.extentions.round import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare -import com.mctech.stocktradetracking.domain.stock_share.entity.StockShareFinalBalance import com.mctech.stocktradetracking.domain.stock_share.interaction.strategies.ComputeBalanceStrategy -class GetFinalBalanceCase : ComputeBalanceStrategy { - override fun execute(stockShareList: List): StockShareFinalBalance { - var balance = 0.0 - var investment = 0.0 - - for (stock in stockShareList) { - investment += stock.getFinalStockPrice() - balance += stock.getBalance() - } - - val variation = if (investment == 0.0) { - 0.0 - } else - (((investment + balance) / investment * 100) - 100).round(2) - - return StockShareFinalBalance( - balance, - investment, - variation - ) - } +class GetFinalBalanceCase : ComputeBalanceStrategy() { + override fun getConsideredBalance(item: StockShare) = item.getBalance() } \ No newline at end of file diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalDailyBalanceCase.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalDailyBalanceCase.kt index 51bd498..613c642 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalDailyBalanceCase.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GetFinalDailyBalanceCase.kt @@ -1,30 +1,8 @@ package com.mctech.stocktradetracking.domain.stock_share.interaction -import com.mctech.stocktradetracking.domain.extentions.round import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare -import com.mctech.stocktradetracking.domain.stock_share.entity.StockShareFinalBalance import com.mctech.stocktradetracking.domain.stock_share.interaction.strategies.ComputeBalanceStrategy -class GetFinalDailyBalanceCase : ComputeBalanceStrategy { - - override fun execute(stockShareList: List): StockShareFinalBalance { - var balance = 0.0 - var investment = 0.0 - - for (stock in stockShareList) { - investment += stock.getFinalStockPrice() - balance += stock.getDailyVariationBalance() - } - - val variation = if (investment == 0.0) { - 0.0 - } else - (((investment + balance) / investment * 100) - 100).round(2) - - return StockShareFinalBalance( - balance, - investment, - variation - ) - } +class GetFinalDailyBalanceCase : ComputeBalanceStrategy() { + override fun getConsideredBalance(item: StockShare) = item.getDailyVariationBalance() } \ No newline at end of file diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/ComputeBalanceStrategy.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/ComputeBalanceStrategy.kt index 220c318..033097c 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/ComputeBalanceStrategy.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/ComputeBalanceStrategy.kt @@ -1,8 +1,34 @@ package com.mctech.stocktradetracking.domain.stock_share.interaction.strategies +import com.mctech.stocktradetracking.domain.extentions.round import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare import com.mctech.stocktradetracking.domain.stock_share.entity.StockShareFinalBalance -interface ComputeBalanceStrategy { - fun execute(stockShareList: List): StockShareFinalBalance +/** + * This class will compute the balance of a list of stocks into a [StockShareFinalBalance] instance. + */ +abstract class ComputeBalanceStrategy { + fun execute(stockShareList: List): StockShareFinalBalance { + var balance = 0.0 + var investment = 0.0 + + for (stock in stockShareList) { + investment += stock.getFinalStockPrice() + balance += getConsideredBalance(stock) + } + + val variation = if (investment == 0.0) { + 0.0 + } else { + (((investment + balance) / investment * 100) - 100).round(2) + } + + return StockShareFinalBalance( + balance, + investment, + variation + ) + } + + abstract fun getConsideredBalance(item: StockShare): Double } \ No newline at end of file diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/FilterStockListStrategy.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/FilterStockListStrategy.kt index d509d68..9bd4c3f 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/FilterStockListStrategy.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/strategies/FilterStockListStrategy.kt @@ -9,10 +9,11 @@ abstract class FilterStockListStrategy( private val groupStockShareListCase: GroupStockShareListCase ) { fun execute(stockShareList: List, filter: StockFilter): List { - val resolvedList = if (filter.isGroupingStock) + val resolvedList = if (filter.isGroupingStock) { groupStockShareListCase.transform(stockShareList) - else + } else { stockShareList + } return sort(resolvedList, filter.sort) } diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareNavigator.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareNavigator.kt index bc6741d..c56898b 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareNavigator.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareNavigator.kt @@ -7,6 +7,7 @@ interface StockShareNavigator { fun fromStockListToBuyPosition() fun fromStockListToFilter() fun fromStockListToEditPosition(stockShare: StockShare) + fun fromEditToSplitPosition(stockShare: StockShare) fun navigateBack() } diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionCommand.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionCommand.kt index 373d462..e346f76 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionCommand.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionCommand.kt @@ -1,7 +1,11 @@ package com.mctech.stocktradetracking.feature.stock_share.edit_position import com.mctech.architecture.mvvm.x.core.ViewCommand +import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare sealed class StockShareEditPositionCommand : ViewCommand { object NavigateBack : StockShareEditPositionCommand() + data class NavigateToSplitScreen( + val stock: StockShare + ) : StockShareEditPositionCommand() } \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt index bdbaf22..e74a3f5 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt @@ -66,6 +66,9 @@ class StockShareEditPositionFragment : Fragment() { ) ) } + R.id.menu_spit_item -> { + viewModel.interact(StockShareEditPositionInteraction.SplitStockShare) + } } return true @@ -92,6 +95,9 @@ class StockShareEditPositionFragment : Fragment() { is StockShareEditPositionCommand.NavigateBack -> { navigator.navigateBack() } + is StockShareEditPositionCommand.NavigateToSplitScreen -> { + navigator.fromEditToSplitPosition(command.stock) + } } } diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionInteraction.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionInteraction.kt index 781b315..6bd2634 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionInteraction.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionInteraction.kt @@ -16,5 +16,6 @@ sealed class StockShareEditPositionInteraction : UserInteraction { ) : StockShareEditPositionInteraction() object DeleteStockShare : StockShareEditPositionInteraction() + object SplitStockShare : StockShareEditPositionInteraction() data class CloseStockPosition(val price: Double?) : StockShareEditPositionInteraction() } \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt index 4f026d1..c4886d0 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt @@ -39,6 +39,11 @@ class StockShareEditPositionViewModel constructor( is StockShareEditPositionInteraction.CloseStockPosition -> closeCurrentStockShareInteraction( interaction.price ) + is StockShareEditPositionInteraction.SplitStockShare -> { + currentStock?.let { + sendCommand(StockShareEditPositionCommand.NavigateToSplitScreen(it)) + } + } } } diff --git a/features/feature-stock-share/src/main/res/menu/stock_share_delete_menu.xml b/features/feature-stock-share/src/main/res/menu/stock_share_delete_menu.xml index f13778a..69f5054 100644 --- a/features/feature-stock-share/src/main/res/menu/stock_share_delete_menu.xml +++ b/features/feature-stock-share/src/main/res/menu/stock_share_delete_menu.xml @@ -2,6 +2,12 @@ + + Confirmation Yes No + Split diff --git a/libraries/library-design-system/src/main/res/drawable/ic_split.xml b/libraries/library-design-system/src/main/res/drawable/ic_split.xml new file mode 100644 index 0000000..1b7c4ca --- /dev/null +++ b/libraries/library-design-system/src/main/res/drawable/ic_split.xml @@ -0,0 +1,10 @@ + + + From 7501eb8f4ad81ebcdf9cdfe4e8d12bcf0f0c9320 Mon Sep 17 00:00:00 2001 From: MayconCardoso Date: Wed, 14 Oct 2020 19:14:23 +0100 Subject: [PATCH 2/8] - Create Split Stock Presentation Layer --- .../navigation/AppNavigatorHandler.kt | 15 +- .../stock_trade_navigation_graph.xml | 15 ++ .../feature/stock_share/StockShareDI.kt | 7 + .../StockSplitPositionCommand.kt | 7 + .../StockSplitPositionFragment.kt | 130 ++++++++++++++++++ .../StockSplitPositionInteraction.kt | 15 ++ .../StockSplitPositionViewModel.kt | 41 ++++++ .../res/layout/fragment_stock_share_split.xml | 72 ++++++++++ .../src/main/res/values/strings.xml | 5 + 9 files changed, 300 insertions(+), 7 deletions(-) create mode 100644 features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionCommand.kt create mode 100644 features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt create mode 100644 features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt create mode 100644 features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt create mode 100644 features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml diff --git a/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt b/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt index 4b7cc42..9acb6f9 100644 --- a/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt +++ b/app/src/main/java/com/mctech/stocktradetracking/navigation/AppNavigatorHandler.kt @@ -7,6 +7,7 @@ import com.mctech.stocktradetracking.R import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare import com.mctech.stocktradetracking.domain.timeline_balance.entity.TimelineBalance import com.mctech.stocktradetracking.feature.stock_share.StockShareNavigator +import com.mctech.stocktradetracking.feature.stock_share.edit_position.StockShareEditPositionFragmentDirections import com.mctech.stocktradetracking.feature.timeline_balance.TimelineBalanceNavigator import com.mctech.stocktradetracking.feature.timeline_balance.list_period.TimelineBalanceListFragmentDirections @@ -39,7 +40,9 @@ object AppNavigatorHandler : } override fun fromEditToSplitPosition(stockShare: StockShare) { - navController?.navigateUp() + val destination = StockShareEditPositionFragmentDirections + .actionStockShareEditPriceFragmentToStockSplitPositionFragment(stockShare) + navController?.navigate(destination) } override fun fromStockListToBuyPosition() { @@ -51,16 +54,14 @@ object AppNavigatorHandler : } override fun fromTimelineToEditPeriod(currentPeriod: TimelineBalance) { - val destination = - TimelineBalanceListFragmentDirections.actionTimelineBalanceFragmentToTimelineBalanceEditPeriodFragment( - currentPeriod - ) + val destination = TimelineBalanceListFragmentDirections + .actionTimelineBalanceFragmentToTimelineBalanceEditPeriodFragment(currentPeriod) navController?.navigate(destination) } override fun fromTimelineToOpenPeriod() { - val destination = - TimelineBalanceListFragmentDirections.actionTimelineBalanceFragmentToTimelineBalanceAddPeriodFragment() + val destination = TimelineBalanceListFragmentDirections + .actionTimelineBalanceFragmentToTimelineBalanceAddPeriodFragment() navController?.navigate(destination) } } \ No newline at end of file diff --git a/app/src/main/res/navigation/stock_trade_navigation_graph.xml b/app/src/main/res/navigation/stock_trade_navigation_graph.xml index 46c2015..c368bf6 100644 --- a/app/src/main/res/navigation/stock_trade_navigation_graph.xml +++ b/app/src/main/res/navigation/stock_trade_navigation_graph.xml @@ -45,6 +45,9 @@ + @@ -109,5 +112,17 @@ app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> + + + + + + \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt index af791e3..e8aee81 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt @@ -3,6 +3,7 @@ package com.mctech.stocktradetracking.feature.stock_share import com.mctech.stocktradetracking.feature.stock_share.add_position.StockShareBuyViewModel import com.mctech.stocktradetracking.feature.stock_share.edit_position.StockShareEditPositionViewModel import com.mctech.stocktradetracking.feature.stock_share.list_position.StockShareListViewModel +import com.mctech.stocktradetracking.feature.stock_share.split_position.StockSplitPositionViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.qualifier.named import org.koin.dsl.module @@ -61,4 +62,10 @@ val stockShareViewModelModule = module { closeStockShareCase = get() ) } + + viewModel { + StockSplitPositionViewModel( + + ) + } } \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionCommand.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionCommand.kt new file mode 100644 index 0000000..deda7c6 --- /dev/null +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionCommand.kt @@ -0,0 +1,7 @@ +package com.mctech.stocktradetracking.feature.stock_share.split_position + +import com.mctech.architecture.mvvm.x.core.ViewCommand + +sealed class StockSplitPositionCommand : ViewCommand { + object NavigateBack : StockSplitPositionCommand() +} \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt new file mode 100644 index 0000000..dcf9845 --- /dev/null +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt @@ -0,0 +1,130 @@ +package com.mctech.stocktradetracking.feature.stock_share.split_position + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import com.google.android.material.textfield.TextInputLayout +import com.mctech.architecture.mvvm.x.core.ComponentState +import com.mctech.architecture.mvvm.x.core.ViewCommand +import com.mctech.architecture.mvvm.x.core.ktx.bindCommand +import com.mctech.architecture.mvvm.x.core.ktx.bindState +import com.mctech.library.keyboard.visibilitymonitor.extentions.closeKeyboard +import com.mctech.library.view.ktx.getValue +import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare +import com.mctech.stocktradetracking.feature.stock_share.R +import com.mctech.stocktradetracking.feature.stock_share.StockShareNavigator +import com.mctech.stocktradetracking.feature.stock_share.databinding.FragmentStockShareSplitBinding +import com.mctech.stocktradetracking.feature.stock_share.stockShareFromBundle +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel + +class StockSplitPositionFragment : Fragment() { + + private val viewModel: StockSplitPositionViewModel by viewModel() + private val navigator: StockShareNavigator by inject() + private var binding: FragmentStockShareSplitBinding? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return FragmentStockShareSplitBinding.inflate(inflater, container, false).let { + binding = it + binding?.lifecycleOwner = this + it.root + } + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + bindCommand(viewModel) { handleCommands(it) } + bindState(viewModel.currentStockShare) { handleStockShareState(it) } + bindListeners() + } + + private fun handleStockShareState(state: ComponentState) { + when (state) { + is ComponentState.Initializing -> { + viewModel.interact( + StockSplitPositionInteraction.OpenStockShareDetails( + stockShareFromBundle(requireArguments()) + ) + ) + } + is ComponentState.Success -> { + binding?.item?.item = state.result + binding?.executePendingBindings() + binding?.item?.executePendingBindings() + } + } + } + + private fun handleCommands(command: ViewCommand) { + when (command) { + is StockSplitPositionCommand.NavigateBack -> { + navigator.navigateBack() + } + } + } + + private fun bindListeners() { + binding?.let { binding -> + binding.btSplit.setOnClickListener { + validateForm(binding) + } + } + } + + private fun validateForm(binding: FragmentStockShareSplitBinding) { + if (isFieldInvalid(binding.tlShareAmount, binding.tlSharePrice)) { + return + } + + confirmationDialog { + viewModel.interact( + StockSplitPositionInteraction.SplitStock( + binding.etShareAmount.getValue().toLong(), + binding.etSharePrice.getValue().toDouble() + ) + ) + } + } + + private fun closeKeyboard() { + activity?.currentFocus?.run { + if (this is EditText) { + context?.closeKeyboard(this) + } + } + } + + private fun isFieldInvalid(vararg fields: TextInputLayout): Boolean { + var isValid = false + fields.forEach { field -> + field.error = "" + if (field.editText?.text?.trim()?.isBlank() == true) { + field.error = getString(R.string.required) + isValid = true + } + } + return isValid + } + + private fun confirmationDialog(blockConfirmation: () -> Unit) { + closeKeyboard() + + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.confirmation) + .setMessage(R.string.confirm_split_position) + .setPositiveButton(R.string.yes) { _, _ -> + blockConfirmation.invoke() + } + .setNegativeButton(R.string.no, null) + .show() + } +} diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt new file mode 100644 index 0000000..f0fff0f --- /dev/null +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt @@ -0,0 +1,15 @@ +package com.mctech.stocktradetracking.feature.stock_share.split_position + +import com.mctech.architecture.mvvm.x.core.UserInteraction +import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare + +sealed class StockSplitPositionInteraction : UserInteraction { + data class OpenStockShareDetails( + val item: StockShare + ) : StockSplitPositionInteraction() + + data class SplitStock( + val splitRatio: Long, + val lastPrice: Double + ) : StockSplitPositionInteraction() +} \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt new file mode 100644 index 0000000..f27a2b2 --- /dev/null +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt @@ -0,0 +1,41 @@ +package com.mctech.stocktradetracking.feature.stock_share.split_position + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.mctech.architecture.mvvm.x.core.BaseViewModel +import com.mctech.architecture.mvvm.x.core.ComponentState +import com.mctech.architecture.mvvm.x.core.UserInteraction +import com.mctech.architecture.mvvm.x.core.ktx.changeToSuccessState +import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare + +class StockSplitPositionViewModel constructor( + +) : BaseViewModel() { + private var currentStock: StockShare? = null + + private val _currentStockShare: MutableLiveData> = + MutableLiveData(ComponentState.Initializing) + val currentStockShare: LiveData> = _currentStockShare + + override suspend fun handleUserInteraction(interaction: UserInteraction) { + when (interaction) { + is StockSplitPositionInteraction.OpenStockShareDetails -> openStockShareInteraction( + interaction.item + ) + is StockSplitPositionInteraction.SplitStock -> splitStockInteraction( + interaction.splitRatio, + interaction.lastPrice + ) + } + } + + private suspend fun splitStockInteraction(splitRatio: Long, lastPrice: Double) { + + } + + private fun openStockShareInteraction(item: StockShare) { + _currentStockShare.changeToSuccessState(item) + currentStock = item + } + +} diff --git a/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml b/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml new file mode 100644 index 0000000..4ce8d4e --- /dev/null +++ b/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/feature-stock-share/src/main/res/values/strings.xml b/features/feature-stock-share/src/main/res/values/strings.xml index 2dd75d3..c976bc7 100644 --- a/features/feature-stock-share/src/main/res/values/strings.xml +++ b/features/feature-stock-share/src/main/res/values/strings.xml @@ -20,4 +20,9 @@ Yes No Split + Split ratio + Split price + Split position + If you continue you will be updating the amount of shares and the buy price according to the split ratio.\n\nAre you sure you want to split this stock? + Required From 54cbf6b0c94cbe6e829e36a1804a800c39b01279 Mon Sep 17 00:00:00 2001 From: MayconCardoso Date: Wed, 14 Oct 2020 21:10:36 +0100 Subject: [PATCH 3/8] - Create Split Stock Data Layer - Create Split Stock Domain Layer - Create Split Stock Tests --- .../stocktradetracking/di/useCasesModule.kt | 77 ++++++++++------ .../stock_trade_navigation_graph.xml | 8 +- .../stock_share/database/StockShareDao.kt | 5 +- .../datasource/LocalStockShareDataSource.kt | 7 +- .../LocalStockShareDataSourceImpl.kt | 8 +- .../repository/StockShareRepository.kt | 6 +- .../repository/StockShareRepositoryTest.kt | 18 ++++ .../domain/stock_share/entity/StockShare.kt | 2 +- .../interaction/GroupStockShareListCase.kt | 3 +- .../interaction/SplitStockShareCase.kt | 34 +++++++ .../stock_share/service/StockShareService.kt | 7 +- .../stock_share/entity/StockShareTest.kt | 6 +- .../interaction/SplitStockShareCaseTest.kt | 92 +++++++++++++++++++ .../feature/stock_share/StockShareDI.kt | 2 +- .../add_position/StockShareBuyFragment.kt | 3 +- .../add_position/StockShareBuyViewModel.kt | 3 +- .../StockShareEditPositionFragment.kt | 17 ++++ .../StockShareEditPositionViewModel.kt | 2 +- .../StockSplitPositionFragment.kt | 5 +- .../StockSplitPositionInteraction.kt | 3 +- .../StockSplitPositionViewModel.kt | 14 ++- .../res/layout/fragment_stock_share_split.xml | 27 +----- .../factories/StockShareDataFactory.kt | 76 ++++++++++++--- 23 files changed, 330 insertions(+), 95 deletions(-) create mode 100644 domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCase.kt create mode 100644 domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCaseTest.kt diff --git a/app/src/main/java/com/mctech/stocktradetracking/di/useCasesModule.kt b/app/src/main/java/com/mctech/stocktradetracking/di/useCasesModule.kt index fc3ab0d..f66844a 100644 --- a/app/src/main/java/com/mctech/stocktradetracking/di/useCasesModule.kt +++ b/app/src/main/java/com/mctech/stocktradetracking/di/useCasesModule.kt @@ -1,6 +1,24 @@ package com.mctech.stocktradetracking.di -import com.mctech.stocktradetracking.domain.stock_share.interaction.* +import com.mctech.stocktradetracking.domain.stock_share.interaction.CloseStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.DeleteStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.EditStockSharePriceCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.FilterStockDailyListCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.FilterStockShareListCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.GetFinalBalanceCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.GetFinalDailyBalanceCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.GetMarketStatusCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.GroupStockShareListCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.ObserveStockClosedListCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.ObserveStockShareListCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SaveStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SelectBestDailyStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SelectBestStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SelectWorstDailyStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SelectWorstStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SellStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SplitStockShareCase +import com.mctech.stocktradetracking.domain.stock_share.interaction.SyncStockSharePriceCase import com.mctech.stocktradetracking.domain.stock_share.interaction.strategies.ComputeBalanceStrategy import com.mctech.stocktradetracking.domain.stock_share.interaction.strategies.FilterStockListStrategy import com.mctech.stocktradetracking.domain.stock_share.interaction.strategies.ObserveStockListStrategy @@ -15,38 +33,39 @@ import org.koin.dsl.module val stockShareUseCasesModule = module { - single { SaveStockShareCase(service = get(), logger = get()) } - single { DeleteStockShareCase(service = get(), logger = get()) } - single { CloseStockShareCase(service = get(), logger = get()) } - single { EditStockSharePriceCase(service = get(), logger = get()) } - single { SellStockShareCase(service = get(), logger = get()) } - single { SyncStockSharePriceCase(service = get(), logger = get()) } - single { GetMarketStatusCase(service = get(), logger = get()) } - single { GetFinalBalanceCase() } - single { GroupStockShareListCase() } - single { ObserveCurrentFilterCase(service = get()) } - - single(named("closedListObserver")) { ObserveStockClosedListCase(service = get()) as ObserveStockListStrategy } - - single(named("stockFilter")) { FilterStockShareListCase(groupStockShareListCase = get()) as FilterStockListStrategy } - single(named("dailyStockFilter")) { FilterStockDailyListCase(groupStockShareListCase = get()) as FilterStockListStrategy } - - single(named("dailyWorstSelector")) { SelectWorstDailyStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } - single(named("dailyBestSelector")) { SelectBestDailyStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } - single(named("stockWorstSelector")) { SelectWorstStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } - single(named("stockBestSelector")) { SelectBestStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } - - single(named("stockBalance")) { GetFinalBalanceCase() as ComputeBalanceStrategy } - single(named("dailyBalance")) { GetFinalDailyBalanceCase() as ComputeBalanceStrategy } + single { SaveStockShareCase(service = get(), logger = get()) } + single { DeleteStockShareCase(service = get(), logger = get()) } + single { CloseStockShareCase(service = get(), logger = get()) } + single { EditStockSharePriceCase(service = get(), logger = get()) } + single { SellStockShareCase(service = get(), logger = get()) } + single { SyncStockSharePriceCase(service = get(), logger = get()) } + single { GetMarketStatusCase(service = get(), logger = get()) } + single { GetFinalBalanceCase() } + single { GroupStockShareListCase() } + single { ObserveCurrentFilterCase(service = get()) } + single { SplitStockShareCase(service = get(), logger = get()) } + + single(named("closedListObserver")) { ObserveStockClosedListCase(service = get()) as ObserveStockListStrategy } + + single(named("stockFilter")) { FilterStockShareListCase(groupStockShareListCase = get()) as FilterStockListStrategy } + single(named("dailyStockFilter")) { FilterStockDailyListCase(groupStockShareListCase = get()) as FilterStockListStrategy } + + single(named("dailyWorstSelector")) { SelectWorstDailyStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } + single(named("dailyBestSelector")) { SelectBestDailyStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } + single(named("stockWorstSelector")) { SelectWorstStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } + single(named("stockBestSelector")) { SelectBestStockShareCase(groupStockShareListCase = get()) as SelectStockStrategy } + + single(named("stockBalance")) { GetFinalBalanceCase() as ComputeBalanceStrategy } + single(named("dailyBalance")) { GetFinalDailyBalanceCase() as ComputeBalanceStrategy } } val stockShareFilterUseCasesModule = module { - single { ObserveStockShareListCase(service = get()) as ObserveStockListStrategy } - single { SaveStockShareFilterCase(service = get(), logger = get()) } + single { ObserveStockShareListCase(service = get()) as ObserveStockListStrategy } + single { SaveStockShareFilterCase(service = get(), logger = get()) } } val timelineUseCasesModule = module { - single { CreatePeriodCase(service = get(), logger = get()) } - single { EditPeriodCase(service = get(), logger = get()) } - single { GetCurrentPeriodBalanceCase(service = get(), logger = get()) } + single { CreatePeriodCase(service = get(), logger = get()) } + single { EditPeriodCase(service = get(), logger = get()) } + single { GetCurrentPeriodBalanceCase(service = get(), logger = get()) } } \ No newline at end of file diff --git a/app/src/main/res/navigation/stock_trade_navigation_graph.xml b/app/src/main/res/navigation/stock_trade_navigation_graph.xml index c368bf6..769911e 100644 --- a/app/src/main/res/navigation/stock_trade_navigation_graph.xml +++ b/app/src/main/res/navigation/stock_trade_navigation_graph.xml @@ -45,9 +45,15 @@ + + app:destination="@id/stockSplitPositionFragment" + app:enterAnim="@anim/nav_default_enter_anim" + app:exitAnim="@anim/nav_default_exit_anim" + app:popEnterAnim="@anim/nav_default_pop_enter_anim" + app:popExitAnim="@anim/nav_default_pop_exit_anim" + app:popUpToInclusive="true" /> diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDao.kt b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDao.kt index 5fea27e..46385fa 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDao.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDao.kt @@ -22,7 +22,6 @@ interface StockShareDao { @Query("SELECT * FROM stock_share WHERE isPositionOpened = 0 ORDER BY code") fun observeStockClosedList(): Flow> - // ============================================================================ // Single shot // ============================================================================ @@ -34,6 +33,10 @@ interface StockShareDao { @Query("SELECT * FROM stock_share WHERE isPositionOpened = 1 AND code = :code LIMIT 1") suspend fun loadStockSharePosition(code: String): StockShare? + @Transaction + @Query("SELECT * FROM stock_share WHERE isPositionOpened = 1 AND code = :code") + suspend fun loadAllStockSharesByCode(code: String): List + @Transaction @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun save(stockShare: StockShareDatabaseEntity) diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSource.kt b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSource.kt index 357256e..3138ef4 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSource.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSource.kt @@ -5,9 +5,12 @@ import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare import kotlinx.coroutines.flow.Flow interface LocalStockShareDataSource { - suspend fun observeStockShareList(): Flow> - suspend fun observeStockClosedList(): Flow> + fun observeStockShareList(): Flow> + fun observeStockClosedList(): Flow> + suspend fun getMarketStatus(): MarketStatus + suspend fun getAllByCode(code: String): List + suspend fun getDistinctStockCode(): List suspend fun saveStockShare(share: StockShare) diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSourceImpl.kt b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSourceImpl.kt index c015654..5555362 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSourceImpl.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/datasource/LocalStockShareDataSourceImpl.kt @@ -13,11 +13,11 @@ class LocalStockShareDataSourceImpl( private val stockShareDao: StockShareDao ) : LocalStockShareDataSource { - override suspend fun observeStockShareList(): Flow> { + override fun observeStockShareList(): Flow> { return stockShareDao.observeAllOpenedPosition() } - override suspend fun observeStockClosedList(): Flow> { + override fun observeStockClosedList(): Flow> { return stockShareDao.observeStockClosedList() } @@ -46,6 +46,10 @@ class LocalStockShareDataSourceImpl( } } + override suspend fun getAllByCode(code: String): List { + return stockShareDao.loadAllStockSharesByCode(code) + } + override suspend fun getDistinctStockCode(): List { return stockShareDao.loadDistinctStockCodes() } diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepository.kt b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepository.kt index 5a0bba6..5ae4712 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepository.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepository.kt @@ -23,9 +23,9 @@ class StockShareRepository( private var syncPrinceScope: CoroutineScope? = null private var syncPricesDefer = mutableListOf>() - override suspend fun observeStockShareList() = localDataSource.observeStockShareList() + override fun observeStockShareList() = localDataSource.observeStockShareList() - override suspend fun observeStockClosedList() = localDataSource.observeStockClosedList() + override fun observeStockClosedList() = localDataSource.observeStockClosedList() override suspend fun saveStockShare(share: StockShare) = localDataSource.saveStockShare(share) @@ -37,6 +37,8 @@ class StockShareRepository( override suspend fun getMarketStatus() = localDataSource.getMarketStatus() + override suspend fun getAllByCode(code: String) = localDataSource.getAllByCode(code) + override suspend fun editStockShareValue(shareCode: String, value: Double) = localDataSource.editStockShareValue( shareCode, diff --git a/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepositoryTest.kt b/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepositoryTest.kt index 0f20c08..3aa97a5 100644 --- a/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepositoryTest.kt +++ b/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/repository/StockShareRepositoryTest.kt @@ -25,6 +25,7 @@ class StockShareRepositoryTest { private val remoteDataSource = mock() private val logger = mock() private val expectedSingle = StockShareDataFactory.single() + private val expectedAll = StockShareDataFactory.listOf() private val expectedMarket = MarketStatus("", true) private val expectedError = RuntimeException() @@ -125,6 +126,23 @@ class StockShareRepositoryTest { } ) + @Test + fun `should return all by code`() = testScenario( + scenario = { + whenever(localDataSource.getAllByCode(any())).thenReturn(expectedAll) + }, + action = { + repository.getAllByCode("MGLU3") + }, + assertions = { + verify(localDataSource).getAllByCode("MGLU3") + verifyNoMoreInteractions(localDataSource) + verifyZeroInteractions(remoteDataSource) + + Assertions.assertThat(it).isEqualTo(expectedAll) + } + ) + @Test fun `should edit stock price`() = testScenario( action = { diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt index fc9e5a4..f2fb99e 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt @@ -15,7 +15,7 @@ data class StockShare( var salePrice: Double = purchasePrice, val purchaseDate: Date = Calendar.getInstance().time, var saleDate: Date? = null, - var isPositionOpened: Boolean = true, + var isPositionOpened: Boolean, var marketChange: Double? = null, var previousClose: Double? = null diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt index 735b74e..53ad003 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt @@ -16,7 +16,8 @@ class GroupStockShareListCase { purchaseDate = stockShare.purchaseDate, salePrice = (acc.getFinalStockPrice() + stockShare.getFinalStockPrice()) / (acc.shareAmount + stockShare.shareAmount), marketChange = stockShare.marketChange, - previousClose = stockShare.previousClose + previousClose = stockShare.previousClose, + isPositionOpened = stockShare.isPositionOpened ) } } diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCase.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCase.kt new file mode 100644 index 0000000..02cd1e8 --- /dev/null +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCase.kt @@ -0,0 +1,34 @@ +package com.mctech.stocktradetracking.domain.stock_share.interaction + +import com.mctech.library.logger.Logger +import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare +import com.mctech.stocktradetracking.domain.stock_share.service.StockShareService + +class SplitStockShareCase( + private val service: StockShareService, + private val logger: Logger +) { + suspend fun execute(share: StockShare, splitRatio: Int) { + try { + // Load all stocks of the code. + val stockPositions = service.getAllByCode(share.code) + + // Compute new buy price + stockPositions.forEach { item -> + // Calculate new amount of shares after split + val newAmountOfShares = item.shareAmount * splitRatio + + // Calculate new base price. + item.purchasePrice = item.getInvestmentValue() / newAmountOfShares + + // Change new amount + item.shareAmount = newAmountOfShares + + // Save it + service.saveStockShare(item) + } + } catch (ex: Exception) { + logger.e(e = ex) + } + } +} diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/service/StockShareService.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/service/StockShareService.kt index 636f3a8..2d4785a 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/service/StockShareService.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/service/StockShareService.kt @@ -5,14 +5,17 @@ import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare import kotlinx.coroutines.flow.Flow interface StockShareService { - suspend fun observeStockShareList(): Flow> - suspend fun observeStockClosedList(): Flow> + fun observeStockShareList(): Flow> + fun observeStockClosedList(): Flow> + suspend fun getMarketStatus(): MarketStatus + suspend fun getAllByCode(code: String): List suspend fun saveStockShare(share: StockShare) suspend fun sellStockShare(share: StockShare) suspend fun deleteStockShare(share: StockShare) suspend fun closeStockShare(share: StockShare) suspend fun editStockShareValue(shareCode: String, value: Double) + suspend fun syncStockSharePrice() } diff --git a/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt b/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt index fb99945..73d2839 100644 --- a/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt +++ b/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt @@ -38,7 +38,8 @@ class StockShareTest { shareAmount = 200, purchasePrice = 27.92, purchaseDate = expectedDate, - previousClose = 0.0 + previousClose = 0.0, + isPositionOpened = true ) private val expectedEmpty = StockShare( @@ -46,7 +47,8 @@ class StockShareTest { code = "MGLU3", shareAmount = 0, purchasePrice = 0.0, - purchaseDate = expectedDate + purchaseDate = expectedDate, + isPositionOpened = true ) @Test diff --git a/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCaseTest.kt b/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCaseTest.kt new file mode 100644 index 0000000..ac0b15f --- /dev/null +++ b/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/interaction/SplitStockShareCaseTest.kt @@ -0,0 +1,92 @@ +package com.mctech.stocktradetracking.domain.stock_share.interaction + +import com.mctech.library.logger.Logger +import com.mctech.stocktradetracking.domain.stock_share.service.StockShareService +import com.mctech.stocktradetracking.testing.data_factory.factories.StockShareDataFactory +import com.mctech.stocktradetracking.testing.data_factory.testScenario +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.times +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test + +class SplitStockShareCaseTest { + private lateinit var useCase: SplitStockShareCase + + private val service = mock() + private val logger = mock() + + private val expectedItems = StockShareDataFactory.splitList() + private val expectedError = RuntimeException() + private val stockInput = StockShareDataFactory.single(code = "MGLU3") + + + @Before + fun `before each test`() { + useCase = SplitStockShareCase(service, logger) + } + + @Test + fun `should log error while loading`() = testScenario( + scenario = { + whenever(service.getAllByCode(any())).thenThrow(expectedError) + }, + action = { + useCase.execute(share = stockInput, splitRatio = 4) + }, + assertions = { + verify(logger).e(e = expectedError) + } + ) + + @Test + fun `should log error while saving`() = testScenario( + scenario = { + whenever(service.saveStockShare(any())).thenThrow(expectedError) + whenever(service.getAllByCode(any())).thenReturn(StockShareDataFactory.listOf(3)) + }, + action = { + useCase.execute(share = stockInput, splitRatio = 4) + }, + assertions = { + verify(logger).e(e = expectedError) + } + ) + + + @Test + fun `should update items`() = testScenario( + scenario = { + whenever(service.getAllByCode(any())).thenReturn(expectedItems) + }, + action = { + useCase.execute(share = stockInput, splitRatio = 4) + }, + assertions = { + verify(service).getAllByCode("MGLU3") + verify(service, times(2)).saveStockShare(any()) + + // Load fresh list again + val validationList = StockShareDataFactory.splitList() + + // Assert first item + val firstExpected = expectedItems[0] + val firstFresh = validationList[0] + assertThat(firstExpected.shareAmount).isEqualTo(firstFresh.shareAmount * 4) + assertThat(firstExpected.purchasePrice).isEqualTo( + firstFresh.getInvestmentValue() / firstExpected.shareAmount + ) + + // Assert second item + val secondExpected = expectedItems[1] + val secondFresh = validationList[1] + assertThat(secondExpected.shareAmount).isEqualTo(secondFresh.shareAmount * 4) + assertThat(secondExpected.purchasePrice).isEqualTo( + secondFresh.getInvestmentValue() / secondExpected.shareAmount + ) + } + ) +} \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt index e8aee81..cb51e9d 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/StockShareDI.kt @@ -65,7 +65,7 @@ val stockShareViewModelModule = module { viewModel { StockSplitPositionViewModel( - + splitStockShareCase = get() ) } } \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt index 305e0c3..bb758b9 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt @@ -92,7 +92,8 @@ class StockShareBuyFragment : Fragment() { val stockShare = StockShare( code = binding.etShareCode.getValue(), purchasePrice = binding.etSharePrice.getValue().toDoubleOrNull() ?: 0.0, - shareAmount = binding.etShareAmount.getValue().toLongOrNull() ?: 0 + shareAmount = binding.etShareAmount.getValue().toLongOrNull() ?: 0, + isPositionOpened = true ) binding.itemInvestmentAmount.text = stockShare.getInvestmentValueDescription() diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt index 907d77a..0dfd776 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt @@ -28,7 +28,8 @@ class StockShareBuyViewModel constructor( code = code.toUpperCase(Locale.getDefault()), shareAmount = amount, purchasePrice = price, - purchaseDate = Calendar.getInstance().time + purchaseDate = Calendar.getInstance().time, + isPositionOpened = true ) ) diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt index e74a3f5..aef40c5 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionFragment.kt @@ -29,6 +29,9 @@ class StockShareEditPositionFragment : Fragment() { private val viewModel: StockShareEditPositionViewModel by viewModel() private val navigator: StockShareNavigator by inject() private var binding: FragmentStockShareEditPriceBinding? = null + private var closeMenuItem: MenuItem? = null + private var splitMenuItem: MenuItem? = null + private var removeMenuItem: MenuItem? = null override fun onCreateView( inflater: LayoutInflater, @@ -52,6 +55,13 @@ class StockShareEditPositionFragment : Fragment() { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.stock_share_delete_menu, menu) + closeMenuItem = menu.findItem(R.id.menu_close_item) + splitMenuItem = menu.findItem(R.id.menu_spit_item) + removeMenuItem = menu.findItem(R.id.menu_delete) + + viewModel.currentStock?.let { stock -> + handleMenuVisibility(stock) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -90,6 +100,13 @@ class StockShareEditPositionFragment : Fragment() { } } + private fun handleMenuVisibility(stockShare: StockShare) { + val isMenuVisible = stockShare.isPositionOpened && stockShare.id != null + closeMenuItem?.isVisible = isMenuVisible + splitMenuItem?.isVisible = isMenuVisible + removeMenuItem?.isVisible = stockShare.id != null + } + private fun handleCommands(command: ViewCommand) { when (command) { is StockShareEditPositionCommand.NavigateBack -> { diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt index c4886d0..260dd37 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/edit_position/StockShareEditPositionViewModel.kt @@ -18,7 +18,7 @@ class StockShareEditPositionViewModel constructor( private val deleteStockShareCase: DeleteStockShareCase, private val closeStockShareCase: CloseStockShareCase ) : BaseViewModel() { - private var currentStock: StockShare? = null + var currentStock: StockShare? = null private val _currentStockShare: MutableLiveData> = MutableLiveData(ComponentState.Initializing) diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt index dcf9845..24d8ea4 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt @@ -81,15 +81,14 @@ class StockSplitPositionFragment : Fragment() { } private fun validateForm(binding: FragmentStockShareSplitBinding) { - if (isFieldInvalid(binding.tlShareAmount, binding.tlSharePrice)) { + if (isFieldInvalid(binding.tlShareAmount)) { return } confirmationDialog { viewModel.interact( StockSplitPositionInteraction.SplitStock( - binding.etShareAmount.getValue().toLong(), - binding.etSharePrice.getValue().toDouble() + binding.etShareAmount.getValue().toInt() ) ) } diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt index f0fff0f..a2f39d0 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionInteraction.kt @@ -9,7 +9,6 @@ sealed class StockSplitPositionInteraction : UserInteraction { ) : StockSplitPositionInteraction() data class SplitStock( - val splitRatio: Long, - val lastPrice: Double + val splitRatio: Int ) : StockSplitPositionInteraction() } \ No newline at end of file diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt index f27a2b2..1e7cefa 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionViewModel.kt @@ -7,9 +7,10 @@ import com.mctech.architecture.mvvm.x.core.ComponentState import com.mctech.architecture.mvvm.x.core.UserInteraction import com.mctech.architecture.mvvm.x.core.ktx.changeToSuccessState import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare +import com.mctech.stocktradetracking.domain.stock_share.interaction.SplitStockShareCase class StockSplitPositionViewModel constructor( - + private val splitStockShareCase: SplitStockShareCase ) : BaseViewModel() { private var currentStock: StockShare? = null @@ -23,14 +24,19 @@ class StockSplitPositionViewModel constructor( interaction.item ) is StockSplitPositionInteraction.SplitStock -> splitStockInteraction( - interaction.splitRatio, - interaction.lastPrice + interaction.splitRatio ) } } - private suspend fun splitStockInteraction(splitRatio: Long, lastPrice: Double) { + private suspend fun splitStockInteraction(splitRatio: Int) { + // Split stock + currentStock?.let {stock -> + splitStockShareCase.execute(stock, splitRatio) + } + // Navigate back + sendCommand(StockSplitPositionCommand.NavigateBack) } private fun openStockShareInteraction(item: StockShare) { diff --git a/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml b/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml index 4ce8d4e..f0a7a63 100644 --- a/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml +++ b/features/feature-stock-share/src/main/res/layout/fragment_stock_share_split.xml @@ -35,27 +35,6 @@ android:singleLine="true" /> - - - - - + app:layout_constraintEnd_toEndOf="@+id/tlShareAmount" + app:layout_constraintStart_toStartOf="@+id/tlShareAmount" + app:layout_constraintTop_toBottomOf="@id/tlShareAmount" /> diff --git a/testing/testing-data-factory/src/main/java/com/mctech/stocktradetracking/testing/data_factory/factories/StockShareDataFactory.kt b/testing/testing-data-factory/src/main/java/com/mctech/stocktradetracking/testing/data_factory/factories/StockShareDataFactory.kt index 4492d4e..604e955 100644 --- a/testing/testing-data-factory/src/main/java/com/mctech/stocktradetracking/testing/data_factory/factories/StockShareDataFactory.kt +++ b/testing/testing-data-factory/src/main/java/com/mctech/stocktradetracking/testing/data_factory/factories/StockShareDataFactory.kt @@ -43,7 +43,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 2.0, - previousClose = 8.0 + previousClose = 8.0, + isPositionOpened = true ) ) @@ -55,7 +56,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 2.0, - previousClose = 8.0 + previousClose = 8.0, + isPositionOpened = true ) ) } @@ -71,7 +73,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, previousClose = 10.0, - marketChange = 2.0 + marketChange = 2.0, + isPositionOpened = true ) ) @@ -83,7 +86,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, previousClose = 10.0, - marketChange = 2.0 + marketChange = 2.0, + isPositionOpened = true ) ) } @@ -99,7 +103,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 10.0 + previousClose = 10.0, + isPositionOpened = true ) ) @@ -111,7 +116,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 10.0 + previousClose = 10.0, + isPositionOpened = true ) ) } @@ -127,7 +133,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 2.0, - previousClose = 8.0 + previousClose = 8.0, + isPositionOpened = true ) ) @@ -139,7 +146,8 @@ object StockShareDataFactory { shareAmount = 400, purchaseDate = Calendar.getInstance().time, marketChange = 2.0, - previousClose = 8.0 + previousClose = 8.0, + isPositionOpened = true ) ) @@ -151,7 +159,8 @@ object StockShareDataFactory { shareAmount = 200, purchaseDate = Calendar.getInstance().time, marketChange = 2.0, - previousClose = 8.0 + previousClose = 8.0, + isPositionOpened = true ) ) @@ -163,7 +172,8 @@ object StockShareDataFactory { shareAmount = 400, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 10.0 + previousClose = 10.0, + isPositionOpened = true ) ) @@ -175,7 +185,8 @@ object StockShareDataFactory { shareAmount = 200, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 10.0 + previousClose = 10.0, + isPositionOpened = true ) ) @@ -187,7 +198,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 2.0 + previousClose = 2.0, + isPositionOpened = true ) ) } @@ -203,7 +215,8 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 2.0, - previousClose = 8.0 + previousClose = 8.0, + isPositionOpened = true ) ) @@ -215,7 +228,8 @@ object StockShareDataFactory { shareAmount = 200, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 10.0 + previousClose = 10.0, + isPositionOpened = true ) ) @@ -227,9 +241,41 @@ object StockShareDataFactory { shareAmount = 100, purchaseDate = Calendar.getInstance().time, marketChange = 0.0, - previousClose = 2.0 + previousClose = 2.0, + isPositionOpened = true + ) + ) + } + } + + fun splitList(): List { + return mutableListOf().apply { + add( + StockShare( + code = "MGLU3", + purchasePrice = 10.0, + salePrice = 8.0, + shareAmount = 100, + purchaseDate = Calendar.getInstance().time, + previousClose = 10.0, + marketChange = 2.0, + isPositionOpened = true + ) + ) + + add( + StockShare( + code = "MGLU3", + purchasePrice = 20.0, + salePrice = 8.0, + shareAmount = 200, + purchaseDate = Calendar.getInstance().time, + previousClose = 10.0, + marketChange = 2.0, + isPositionOpened = true ) ) } } + } \ No newline at end of file From 08b2ece4e1de4acedfc6ad1382b4731e35c69239 Mon Sep 17 00:00:00 2001 From: MayconCardoso Date: Wed, 14 Oct 2020 21:18:50 +0100 Subject: [PATCH 4/8] - Increment version --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e94cde1..8f8e01a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ apply plugin: 'com.adarshr.test-logger' android { defaultConfig { applicationId "com.mctech.stocktradetracking" - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "1.1" multiDexEnabled true } buildTypes { From 293ac7578fc1b058c7f6ba6fa18e3b1c30a3306d Mon Sep 17 00:00:00 2001 From: MayconCardoso Date: Thu, 15 Oct 2020 18:43:36 +0100 Subject: [PATCH 5/8] - Database migration to keep the original structure of the stock after split it. --- .../di/data/databaseModules.kt | 3 +- .../data/database/AppDatabase.kt | 2 +- .../data/database/migration/Migrations.kt | 23 ++++++ .../database/StockShareDatabaseEntity.kt | 2 + .../stock_share/mapper/StockShareMapper.kt | 4 ++ .../mapper/StockShareMapperKtTest.kt | 8 +++ .../domain/stock_share/entity/StockShare.kt | 8 ++- .../interaction/GroupStockShareListCase.kt | 2 + .../stock_share/entity/StockShareTest.kt | 30 ++++++-- .../add_position/StockShareBuyFragment.kt | 11 ++- .../add_position/StockShareBuyViewModel.kt | 2 + .../item_stock_daily_variation_list.xml | 2 +- .../main/res/layout/item_stock_share_list.xml | 16 ++++- .../factories/StockShareDataFactory.kt | 72 ++++++++++++++----- 14 files changed, 153 insertions(+), 32 deletions(-) create mode 100644 data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt diff --git a/app/src/main/java/com/mctech/stocktradetracking/di/data/databaseModules.kt b/app/src/main/java/com/mctech/stocktradetracking/di/data/databaseModules.kt index d8d57b6..8e24d7c 100644 --- a/app/src/main/java/com/mctech/stocktradetracking/di/data/databaseModules.kt +++ b/app/src/main/java/com/mctech/stocktradetracking/di/data/databaseModules.kt @@ -2,6 +2,7 @@ package com.mctech.stocktradetracking.di.data import androidx.room.Room import com.mctech.stocktradetracking.data.database.AppDatabase +import com.mctech.stocktradetracking.data.database.migration.bindMigrations import org.koin.android.ext.koin.androidContext import org.koin.dsl.module @@ -11,7 +12,7 @@ val databaseModule = module { androidContext(), AppDatabase::class.java, "stock-trade-tracking-database" - ).build() + ).bindMigrations().build() } single { diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/database/AppDatabase.kt b/data/src/main/java/com/mctech/stocktradetracking/data/database/AppDatabase.kt index fbd234b..57ab357 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/database/AppDatabase.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/database/AppDatabase.kt @@ -9,7 +9,7 @@ import com.mctech.stocktradetracking.data.timeline_balance.database.TimelineBala import com.mctech.stocktradetracking.data.timeline_balance.database.TimelineBalanceDatabaseEntity @Database( - version = 1, + version = 2, entities = [ StockShareDatabaseEntity::class, TimelineBalanceDatabaseEntity::class diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt b/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt new file mode 100644 index 0000000..3ea8869 --- /dev/null +++ b/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt @@ -0,0 +1,23 @@ +package com.mctech.stocktradetracking.data.database.migration + +import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +object Migrations { + val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE stock_share ADD COLUMN initialShareAmount INTEGER NOT NULL default 0") + database.execSQL("ALTER TABLE stock_share ADD COLUMN initialPurchasePrice REAL NOT NULL default 0.0") + database.execSQL("UPDATE stock_share set initialShareAmount = shareAmount") + database.execSQL("UPDATE stock_share set initialPurchasePrice = purchasePrice") + } + } +} + +fun RoomDatabase.Builder.bindMigrations(): RoomDatabase.Builder { + addMigrations( + Migrations.MIGRATION_1_2 + ) + return this +} \ No newline at end of file diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDatabaseEntity.kt b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDatabaseEntity.kt index 3cda30b..af7a00f 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDatabaseEntity.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/database/StockShareDatabaseEntity.kt @@ -22,7 +22,9 @@ data class StockShareDatabaseEntity( @PrimaryKey val id: Long? = null, val code: String, + val initialShareAmount: Long, val shareAmount: Long, + val initialPurchasePrice: Double, val purchasePrice: Double, var salePrice: Double = purchasePrice, val purchaseDate: Date, diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapper.kt b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapper.kt index 448e1e7..6c0c5ea 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapper.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapper.kt @@ -6,7 +6,9 @@ import com.mctech.stocktradetracking.domain.stock_share.entity.StockShare fun StockShare.toDatabaseEntity() = StockShareDatabaseEntity( id = id, code = code, + initialShareAmount = initialShareAmount, shareAmount = shareAmount, + initialPurchasePrice = initialPurchasePrice, purchasePrice = purchasePrice, salePrice = salePrice, purchaseDate = purchaseDate, @@ -19,7 +21,9 @@ fun StockShare.toDatabaseEntity() = StockShareDatabaseEntity( fun StockShareDatabaseEntity.toBusinessEntity() = StockShare( id = id, code = code, + initialShareAmount = initialShareAmount, shareAmount = shareAmount, + initialPurchasePrice = initialPurchasePrice, purchasePrice = purchasePrice, salePrice = salePrice, purchaseDate = purchaseDate, diff --git a/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapperKtTest.kt b/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapperKtTest.kt index 897c5d3..0f4ea76 100644 --- a/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapperKtTest.kt +++ b/data/src/test/java/com/mctech/stocktradetracking/data/stock_share/mapper/StockShareMapperKtTest.kt @@ -13,7 +13,9 @@ class StockShareMapperKtTest { val share = StockShare( id = 1, code = "MGLU3", + initialShareAmount = 200, shareAmount = 1000, + initialPurchasePrice = 100.0, purchasePrice = 45.89, salePrice = 50.91, purchaseDate = Calendar.getInstance().time, @@ -27,7 +29,9 @@ class StockShareMapperKtTest { Assertions.assertThat(share.id).isEqualTo(target.id) Assertions.assertThat(share.code).isEqualTo(target.code) + Assertions.assertThat(share.initialShareAmount).isEqualTo(target.initialShareAmount) Assertions.assertThat(share.shareAmount).isEqualTo(target.shareAmount) + Assertions.assertThat(share.initialPurchasePrice).isEqualTo(target.initialPurchasePrice) Assertions.assertThat(share.purchasePrice).isEqualTo(target.purchasePrice) Assertions.assertThat(share.purchaseDate).isEqualTo(target.purchaseDate) Assertions.assertThat(share.salePrice).isEqualTo(target.salePrice) @@ -42,7 +46,9 @@ class StockShareMapperKtTest { val share = StockShareDatabaseEntity( id = 1, code = "MGLU3", + initialShareAmount = 200, shareAmount = 1000, + initialPurchasePrice = 100.0, purchasePrice = 45.89, salePrice = 50.91, purchaseDate = Calendar.getInstance().time, @@ -56,7 +62,9 @@ class StockShareMapperKtTest { Assertions.assertThat(share.id).isEqualTo(target.id) Assertions.assertThat(share.code).isEqualTo(target.code) + Assertions.assertThat(share.initialShareAmount).isEqualTo(target.initialShareAmount) Assertions.assertThat(share.shareAmount).isEqualTo(target.shareAmount) + Assertions.assertThat(share.initialPurchasePrice).isEqualTo(target.initialPurchasePrice) Assertions.assertThat(share.purchasePrice).isEqualTo(target.purchasePrice) Assertions.assertThat(share.purchaseDate).isEqualTo(target.purchaseDate) Assertions.assertThat(share.salePrice).isEqualTo(target.salePrice) diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt index f2fb99e..5f48d37 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/entity/StockShare.kt @@ -10,7 +10,9 @@ import java.util.Date data class StockShare( val id: Long? = null, var code: String, + var initialShareAmount: Long, var shareAmount: Long, + var initialPurchasePrice: Double, var purchasePrice: Double, var salePrice: Double = purchasePrice, val purchaseDate: Date = Calendar.getInstance().time, @@ -21,8 +23,12 @@ data class StockShare( var previousClose: Double? = null ) : Serializable { + fun getCurrentMomentDescription(): String { + return "NOW $shareAmount @ ${salePrice.formatBrazilianCurrency()}" + } + fun getBuyDescription(): String { - return "BUY $shareAmount @ ${purchasePrice.formatBrazilianCurrency()}" + return "BUY $initialShareAmount @ ${initialPurchasePrice.formatBrazilianCurrency()}" } fun getSellDescription(): String { diff --git a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt index 53ad003..b68d701 100644 --- a/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt +++ b/domain/src/main/java/com/mctech/stocktradetracking/domain/stock_share/interaction/GroupStockShareListCase.kt @@ -11,7 +11,9 @@ class GroupStockShareListCase { it.reduce { acc, stockShare -> StockShare( code = stockShare.code, + initialShareAmount = acc.initialShareAmount + stockShare.initialShareAmount, shareAmount = acc.shareAmount + stockShare.shareAmount, + initialPurchasePrice = (acc.getInvestmentValue() + stockShare.getInvestmentValue()) / (acc.initialShareAmount + stockShare.initialShareAmount), purchasePrice = (acc.getInvestmentValue() + stockShare.getInvestmentValue()) / (acc.shareAmount + stockShare.shareAmount), purchaseDate = stockShare.purchaseDate, salePrice = (acc.getFinalStockPrice() + stockShare.getFinalStockPrice()) / (acc.shareAmount + stockShare.shareAmount), diff --git a/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt b/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt index 73d2839..a5d4ca1 100644 --- a/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt +++ b/domain/src/test/kotlin/com/mctech/stocktradetracking/domain/stock_share/entity/StockShareTest.kt @@ -9,6 +9,8 @@ class StockShareTest { private val expectedValue = StockShare( id = 1, code = "MGLU3", + initialShareAmount = 100, + initialPurchasePrice = 20.0, shareAmount = 200, purchasePrice = 27.92, salePrice = 50.65, @@ -22,6 +24,8 @@ class StockShareTest { private val expectedNegativeValue = StockShare( id = 1, code = "MGLU3", + initialShareAmount = 100, + initialPurchasePrice = 20.0, shareAmount = 200, purchasePrice = 45.03, salePrice = 40.47, @@ -35,6 +39,8 @@ class StockShareTest { private val expectedDefaultValue = StockShare( id = 1, code = "MGLU3", + initialShareAmount = 100, + initialPurchasePrice = 20.0, shareAmount = 200, purchasePrice = 27.92, purchaseDate = expectedDate, @@ -45,6 +51,8 @@ class StockShareTest { private val expectedEmpty = StockShare( id = 1, code = "MGLU3", + initialShareAmount = 100, + initialPurchasePrice = 20.0, shareAmount = 0, purchasePrice = 0.0, purchaseDate = expectedDate, @@ -55,6 +63,8 @@ class StockShareTest { fun `should assert entity`() { assertThat(expectedValue.id).isEqualTo(1) assertThat(expectedValue.code).isEqualTo("MGLU3") + assertThat(expectedValue.initialShareAmount).isEqualTo(100) + assertThat(expectedValue.initialPurchasePrice).isEqualTo(20.00) assertThat(expectedValue.shareAmount).isEqualTo(200) assertThat(expectedValue.purchasePrice).isEqualTo(27.92) assertThat(expectedValue.salePrice).isEqualTo(50.65) @@ -69,6 +79,8 @@ class StockShareTest { fun `should assert default entity`() { assertThat(expectedDefaultValue.id).isEqualTo(1) assertThat(expectedDefaultValue.code).isEqualTo("MGLU3") + assertThat(expectedValue.initialShareAmount).isEqualTo(100) + assertThat(expectedValue.initialPurchasePrice).isEqualTo(20.00) assertThat(expectedDefaultValue.shareAmount).isEqualTo(200) assertThat(expectedDefaultValue.purchasePrice).isEqualTo(27.92) assertThat(expectedDefaultValue.salePrice).isEqualTo(27.92) @@ -80,11 +92,19 @@ class StockShareTest { } @Test - fun `should format buy description`() { - assertThat(expectedValue.getBuyDescription()).isEqualTo("BUY 200 @ R$27,92") - assertThat(expectedDefaultValue.getBuyDescription()).isEqualTo("BUY 200 @ R$27,92") - assertThat(expectedNegativeValue.getBuyDescription()).isEqualTo("BUY 200 @ R$45,03") - assertThat(expectedEmpty.getBuyDescription()).isEqualTo("BUY 0 @ R$0,00") + fun `should format current moment description`() { + assertThat(expectedValue.getCurrentMomentDescription()).isEqualTo("NOW 200 @ R$50,65") + assertThat(expectedDefaultValue.getCurrentMomentDescription()).isEqualTo("NOW 200 @ R$27,92") + assertThat(expectedNegativeValue.getCurrentMomentDescription()).isEqualTo("NOW 200 @ R$40,47") + assertThat(expectedEmpty.getCurrentMomentDescription()).isEqualTo("NOW 0 @ R$0,00") + } + + @Test + fun `should format initial buy description`() { + assertThat(expectedValue.getBuyDescription()).isEqualTo("BUY 100 @ R$20,00") + assertThat(expectedDefaultValue.getBuyDescription()).isEqualTo("BUY 100 @ R$20,00") + assertThat(expectedNegativeValue.getBuyDescription()).isEqualTo("BUY 100 @ R$20,00") + assertThat(expectedEmpty.getBuyDescription()).isEqualTo("BUY 100 @ R$20,00") } @Test diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt index bb758b9..91e97a8 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyFragment.kt @@ -89,11 +89,16 @@ class StockShareBuyFragment : Fragment() { private fun computeInvestment() { binding?.let { binding -> + val purchasePrice = binding.etSharePrice.getValue().toDoubleOrNull() ?: 0.0 + val shareAmount = binding.etShareAmount.getValue().toLongOrNull() ?: 0 + val stockShare = StockShare( code = binding.etShareCode.getValue(), - purchasePrice = binding.etSharePrice.getValue().toDoubleOrNull() ?: 0.0, - shareAmount = binding.etShareAmount.getValue().toLongOrNull() ?: 0, - isPositionOpened = true + purchasePrice = purchasePrice, + shareAmount = shareAmount, + isPositionOpened = true, + initialShareAmount = shareAmount, + initialPurchasePrice = purchasePrice ) binding.itemInvestmentAmount.text = stockShare.getInvestmentValueDescription() diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt index 0dfd776..c2663c3 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/add_position/StockShareBuyViewModel.kt @@ -26,7 +26,9 @@ class StockShareBuyViewModel constructor( saveStockShareCase.execute( StockShare( code = code.toUpperCase(Locale.getDefault()), + initialShareAmount = amount, shareAmount = amount, + initialPurchasePrice = price, purchasePrice = price, purchaseDate = Calendar.getInstance().time, isPositionOpened = true diff --git a/features/feature-stock-share/src/main/res/layout/item_stock_daily_variation_list.xml b/features/feature-stock-share/src/main/res/layout/item_stock_daily_variation_list.xml index b93d209..6c14c2e 100644 --- a/features/feature-stock-share/src/main/res/layout/item_stock_daily_variation_list.xml +++ b/features/feature-stock-share/src/main/res/layout/item_stock_daily_variation_list.xml @@ -59,7 +59,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" - android:text="@{item.currentPriceDescription}" + android:text="@{item.currentMomentDescription}" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@+id/itemCode" diff --git a/features/feature-stock-share/src/main/res/layout/item_stock_share_list.xml b/features/feature-stock-share/src/main/res/layout/item_stock_share_list.xml index b8eb825..6e5eda5 100644 --- a/features/feature-stock-share/src/main/res/layout/item_stock_share_list.xml +++ b/features/feature-stock-share/src/main/res/layout/item_stock_share_list.xml @@ -61,11 +61,22 @@ android:layout_marginBottom="8dp" android:text="@{item.buyDescription}" android:textAppearance="@style/TextAppearance.AppCompat.Small" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@+id/itemCode" app:layout_constraintTop_toBottomOf="@id/itemCode" tools:text="BUY 300 @ R$26,91" /> + + Date: Thu, 15 Oct 2020 19:35:59 +0100 Subject: [PATCH 6/8] Update data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt --- .../stocktradetracking/data/database/migration/Migrations.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt b/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt index 3ea8869..d7c37e1 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt @@ -9,7 +9,7 @@ object Migrations { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE stock_share ADD COLUMN initialShareAmount INTEGER NOT NULL default 0") database.execSQL("ALTER TABLE stock_share ADD COLUMN initialPurchasePrice REAL NOT NULL default 0.0") - database.execSQL("UPDATE stock_share set initialShareAmount = shareAmount") + database.execSQL("UPDATE stock_share set initialShareAmount = shareAmount") database.execSQL("UPDATE stock_share set initialPurchasePrice = purchasePrice") } } @@ -20,4 +20,4 @@ fun RoomDatabase.Builder.bindMigrations(): RoomDatabase.Bu Migrations.MIGRATION_1_2 ) return this -} \ No newline at end of file +} From ee68a727916a0489b91dbf38b5b214a7f44de811 Mon Sep 17 00:00:00 2001 From: Maycon Cardoso Date: Thu, 15 Oct 2020 19:36:15 +0100 Subject: [PATCH 7/8] Update data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt --- .../stocktradetracking/data/database/migration/Migrations.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt b/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt index d7c37e1..aeae326 100644 --- a/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt +++ b/data/src/main/java/com/mctech/stocktradetracking/data/database/migration/Migrations.kt @@ -10,7 +10,7 @@ object Migrations { database.execSQL("ALTER TABLE stock_share ADD COLUMN initialShareAmount INTEGER NOT NULL default 0") database.execSQL("ALTER TABLE stock_share ADD COLUMN initialPurchasePrice REAL NOT NULL default 0.0") database.execSQL("UPDATE stock_share set initialShareAmount = shareAmount") - database.execSQL("UPDATE stock_share set initialPurchasePrice = purchasePrice") + database.execSQL("UPDATE stock_share set initialPurchasePrice = purchasePrice") } } } From 9fb64d138afbc64224cbb8b5b86a2bbdb13de403 Mon Sep 17 00:00:00 2001 From: Maycon Cardoso Date: Thu, 15 Oct 2020 19:46:12 +0100 Subject: [PATCH 8/8] Update features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt --- .../stock_share/split_position/StockSplitPositionFragment.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt index 24d8ea4..68c21de 100644 --- a/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt +++ b/features/feature-stock-share/src/main/java/com/mctech/stocktradetracking/feature/stock_share/split_position/StockSplitPositionFragment.kt @@ -39,8 +39,6 @@ class StockSplitPositionFragment : Fragment() { it.root } } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { bindCommand(viewModel) { handleCommands(it) } bindState(viewModel.currentStockShare) { handleStockShareState(it) }