Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions feature/shares/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
3 changes: 3 additions & 0 deletions feature/shares/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# :feature:shares module
## Dependency graph
// need to create --> ![Dependency graph](../../docs/images/graphs/dep_graph_feature_shares.svg)
46 changes: 46 additions & 0 deletions feature/shares/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("mifos.cmp.feature")
}

android {
namespace = "com.mifos.androidclient.features.shares"
}

kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.jb.lifecycleViewmodel)
api(projects.core.model)
api(projects.core.common)
}

androidMain.dependencies {
implementation(libs.androidx.core.ktx)
}
}
}
dependencies {
implementation(projects.cmpShared)
implementation(projects.core.ui)
implementation(projects.core.domain)
implementation(projects.core.designsystem)
implementation(projects.core.datastore)
implementation(projects.core.network)
implementation(projects.core.database)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.ui)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
implementation(libs.kotlinx.serialization.json)
implementation(libs.jb.lifecycleViewmodel)
implementation(libs.jb.lifecycleruntime)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.androidx.recyclerview)
implementation(libs.google.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.espresso.core)
}
Empty file.
21 changes: 21 additions & 0 deletions feature/shares/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
37 changes: 37 additions & 0 deletions feature/shares/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.

See https://github.com/openMF/android-client/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.camera.any"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<application>

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/feature_client_pinpoint_google_maps_key" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/feature_client_file_provider_paths">
</meta-data>
</provider>

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mifos.androidclient.features.shares.data.models

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class Charge(
val title: String,
val type: String,
val collectedOn: String,
val amount: String
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mifos.androidclient.features.shares.data.models

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class ShareAccountData(
val productName: String,
val externalId: String,
val submittedDate: String,
val currency: String,
val currentPrice: String,
val totalNumberOfShares: String,
val defaultSavingsAccount: String,
val applicationDate: String,
val allowDividends: Boolean,
val minimumActivePeriod: String,
val lockInPeriod: String,
val charges: List<Charge>
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.mifos.androidclient.features.shares.preview

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.mifos.androidclient.databinding.ItemChargeBinding
import com.mifos.androidclient.features.shares.data.models.Charge

class ChargesAdapter : ListAdapter<Charge, ChargesAdapter.ChargeViewHolder>(ChargeDiffCallback()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChargeViewHolder {
val binding = ItemChargeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ChargeViewHolder(binding)
}

override fun onBindViewHolder(holder: ChargeViewHolder, position: Int) {
val charge = getItem(position)
holder.bind(charge)
}

class ChargeViewHolder(private val binding: ItemChargeBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(charge: Charge) {
binding.chargeTitleTv.text = charge.title
binding.chargeAmountTv.text = charge.amount
}
}
}

class ChargeDiffCallback : DiffUtil.ItemCallback<Charge>() {
override fun areItemsTheSame(oldItem: Charge, newItem: Charge): Boolean {
return oldItem.title == newItem.title && oldItem.amount == newItem.amount
}

override fun areContentsTheSame(oldItem: Charge, newItem: Charge): Boolean {
return oldItem == newItem
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.mifos.androidclient.features.shares.preview

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mifos.androidclient.features.shares.data.models.Charge
import com.mifos.androidclient.features.shares.data.models.ShareAccountData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class ShareAccountCreationViewModel : ViewModel() {

private val _shareAccountData = MutableStateFlow<ShareAccountData?>(null)
val shareAccountData: StateFlow<ShareAccountData?> = _shareAccountData.asStateFlow()

fun updateShareAccountData(data: ShareAccountData) {
_shareAccountData.value = data
}

fun populateWithMockData() {
val mockData = ShareAccountData(
productName = "Wallet",
externalId = "33333",
submittedDate = "09 June 2025",
currency = "USD",
currentPrice = "$10,000",
totalNumberOfShares = "10",
defaultSavingsAccount = "Nobi",
applicationDate = "26-09-2025",
allowDividends = true,
minimumActivePeriod = "2 Months",
lockInPeriod = "6 Weeks",
charges = listOf(
Charge("Savings Administration", "Flat", "10-06-2025", "$2"),
Charge("Account Opening Fee", "Flat", "26-09-2025", "$5")
)
)
_shareAccountData.value = mockData
}

fun submitNewShareAccount() {
viewModelScope.launch {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.mifos.androidclient.features.shares.preview

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.mifos.androidclient.databinding.FragmentShareAccountPreviewBinding
import com.mifos.androidclient.features.shares.data.models.ShareAccountData

class ShareAccountPreviewFragment : Fragment() {

private var _binding: FragmentShareAccountPreviewBinding? = null
private val binding get() = _binding!!
private val viewModel: ShareAccountCreationViewModel by activityViewModels()
private val chargesAdapter = ChargesAdapter()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentShareAccountPreviewBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// For isolated testing, populate the ViewModel with mock data.
// Remove this line once the previous steps are implemented and passing data.
viewModel.populateWithMockData()

binding.chargesRv.apply {
layoutManager = LinearLayoutManager(context)
adapter = chargesAdapter
}

viewModel.shareAccountData.observe(viewLifecycleOwner) { data ->
data?.let {
updateUI(it)
chargesAdapter.submitList(it.charges)
}
}

binding.backBtn.setOnClickListener {
findNavController().popBackStack()
}

binding.nextBtn.setOnClickListener {
if (viewModel.shareAccountData.value != null) {
viewModel.submitNewShareAccount()
} else {
Toast.makeText(context, "Data is missing. Please go back to previous steps.", Toast.LENGTH_SHORT).show()
}
}

binding.viewChargesBtn.setOnClickListener {
val charges = viewModel.shareAccountData.value?.charges ?: emptyList()
ViewChargesBottomSheetFragment.newInstance(charges).show(
childFragmentManager, "ViewChargesModal"
)
}
}

private fun updateUI(data: ShareAccountData) {
binding.productNameTv.text = "Product Name: ${data.productName}"
binding.externalIdTv.text = "External ID: ${data.externalId}"
binding.submittedDateTv.text = "Submitted Date: ${data.submittedDate}"

binding.currencyTv.text = "Currency: ${data.currency}"
binding.currentPriceTv.text = "Current Price: ${data.currentPrice}"
binding.totalSharesTv.text = "Total Shares: ${data.totalNumberOfShares}"
binding.defaultSavingsAccountTv.text = "Default Savings Account: ${data.defaultSavingsAccount}"
binding.applicationDateTv.text = "Application Date: ${data.applicationDate}"
binding.allowDividendsTv.text = "Allow Dividends: ${if (data.allowDividends) "Yes" else "No"}"
binding.minimumActivePeriodTv.text = "Minimum Active Period: ${data.minimumActivePeriod}"
binding.lockInPeriodTv.text = "Lock-in Period: ${data.lockInPeriod}"
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.mifos.androidclient.features.shares.preview

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.mifos.androidclient.databinding.BottomSheetViewChargesBinding
import com.mifos.androidclient.features.shares.data.models.Charge

class ViewChargesBottomSheetFragment : BottomSheetDialogFragment() {

private var _binding: BottomSheetViewChargesBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = BottomSheetViewChargesBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val charges = arguments?.getParcelableArrayList<Charge>(CHARGES_KEY) ?: emptyList()
val adapter = ChargesAdapter()
binding.chargesListRv.apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
}
adapter.submitList(charges)

binding.cancelBtn.setOnClickListener {
dismiss()
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

companion object {
const val CHARGES_KEY = "charges_key"

fun newInstance(charges: List<Charge>): ViewChargesBottomSheetFragment {
return ViewChargesBottomSheetFragment().apply {
arguments = bundleOf(CHARGES_KEY to ArrayList(charges))
}
}
}
}
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,4 @@ include(":feature:search")
include(":feature:settings")
//include(":feature:passcode")
include(":feature:search")
include(":feature:shares")
Loading