Skip to content

Commit 443bfa1

Browse files
authored
Refactor - GroupList screen to compose UI (#2079)
* refactor/GroupList screen to compose UI * fix/GroupList screen to compose UI * - Created new Testing Module - Added Unit & Ui Test Classes * Feat - Resolved conflicts & Applied Convention Plugins - Fixed dependencies declaration - Moved files to respective module - Applied convention plugins - Fixed lint issue * - Added Preview Composable
1 parent 8112ffc commit 443bfa1

File tree

48 files changed

+2136
-395
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2136
-395
lines changed

core/common/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
alias(libs.plugins.mifos.android.library)
3+
alias(libs.plugins.mifos.android.hilt)
34
alias(libs.plugins.mifos.android.library.jacoco)
45
alias(libs.plugins.secrets)
56
}
@@ -24,4 +25,6 @@ dependencies {
2425
androidTestImplementation(libs.androidx.test.espresso.core)
2526

2627
implementation(libs.converter.gson)
28+
29+
implementation(libs.javax.inject)
2730
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
package com.mifos.core.common.network
3+
4+
import javax.inject.Qualifier
5+
import kotlin.annotation.AnnotationRetention.RUNTIME
6+
7+
@Qualifier
8+
@Retention(RUNTIME)
9+
annotation class Dispatcher(val mifosDispatcher: MifosDispatchers)
10+
11+
enum class MifosDispatchers {
12+
Default,
13+
IO,
14+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.mifos.core.common.network.di
2+
3+
import com.mifos.core.common.network.Dispatcher
4+
import com.mifos.core.common.network.MifosDispatchers.Default
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
import kotlinx.coroutines.CoroutineDispatcher
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.SupervisorJob
12+
import javax.inject.Qualifier
13+
import javax.inject.Singleton
14+
15+
@Retention(AnnotationRetention.RUNTIME)
16+
@Qualifier
17+
annotation class ApplicationScope
18+
19+
@Module
20+
@InstallIn(SingletonComponent::class)
21+
internal object CoroutineScopesModule {
22+
@Provides
23+
@Singleton
24+
@ApplicationScope
25+
fun providesCoroutineScope(
26+
@Dispatcher(Default) dispatcher: CoroutineDispatcher,
27+
): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.mifos.core.common.network.di
2+
3+
import com.mifos.core.common.network.Dispatcher
4+
import com.mifos.core.common.network.MifosDispatchers.Default
5+
import com.mifos.core.common.network.MifosDispatchers.IO
6+
import dagger.Module
7+
import dagger.Provides
8+
import dagger.hilt.InstallIn
9+
import dagger.hilt.components.SingletonComponent
10+
import kotlinx.coroutines.CoroutineDispatcher
11+
import kotlinx.coroutines.Dispatchers
12+
13+
@Module
14+
@InstallIn(SingletonComponent::class)
15+
object DispatchersModule {
16+
@Provides
17+
@Dispatcher(IO)
18+
fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO
19+
20+
@Provides
21+
@Dispatcher(Default)
22+
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
23+
}

core/data/src/main/java/com/mifos/core/data/di/DataModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.mifos.core.data.di
22

33
import com.mifos.core.data.repository.CheckerInboxTasksRepository
4+
import com.mifos.core.data.repository.GroupsListRepository
45
import com.mifos.core.data.repository_imp.CheckerInboxTasksRepositoryImp
6+
import com.mifos.core.data.repository_imp.GroupsListRepositoryImpl
57
import dagger.Binds
68
import dagger.Module
79
import dagger.hilt.InstallIn
@@ -13,4 +15,9 @@ abstract class DataModule {
1315

1416
@Binds
1517
abstract fun bindCheckerInboxTasksRepository(impl: CheckerInboxTasksRepositoryImp): CheckerInboxTasksRepository
18+
19+
@Binds
20+
internal abstract fun provideGroupListRepository(
21+
groupsListRepositoryImpl: GroupsListRepositoryImpl
22+
): GroupsListRepository
1623
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.mifos.core.data.repository
2+
3+
import com.mifos.core.objects.group.Group
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface GroupsListRepository {
7+
8+
suspend fun getAllGroups(paged: Boolean, offset: Int, limit: Int): List<Group>
9+
10+
fun getAllLocalGroups(): Flow<List<Group>>
11+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.mifos.core.data.repository_imp
2+
3+
import com.mifos.core.data.repository.GroupsListRepository
4+
import com.mifos.core.network.datamanager.DataManagerGroups
5+
import com.mifos.core.objects.client.Page
6+
import com.mifos.core.objects.group.Group
7+
import kotlinx.coroutines.flow.Flow
8+
import kotlinx.coroutines.flow.flow
9+
import kotlinx.coroutines.suspendCancellableCoroutine
10+
import rx.Subscriber
11+
import rx.android.schedulers.AndroidSchedulers
12+
import rx.schedulers.Schedulers
13+
import javax.inject.Inject
14+
import kotlin.coroutines.resume
15+
import kotlin.coroutines.resumeWithException
16+
import kotlin.coroutines.suspendCoroutine
17+
18+
class GroupsListRepositoryImpl @Inject constructor(
19+
private val dataManager: DataManagerGroups,
20+
) : GroupsListRepository {
21+
override suspend fun getAllGroups(paged: Boolean, offset: Int, limit: Int): List<Group> {
22+
return suspendCancellableCoroutine { continuation ->
23+
dataManager
24+
.getGroups(paged, offset, limit)
25+
.observeOn(AndroidSchedulers.mainThread())
26+
.subscribeOn(Schedulers.io())
27+
.subscribe(object : Subscriber<Page<Group>>() {
28+
override fun onCompleted() {
29+
}
30+
31+
override fun onError(error: Throwable) {
32+
continuation.resumeWithException(error)
33+
}
34+
35+
override fun onNext(groups: Page<Group>) {
36+
continuation.resume(groups.pageItems)
37+
}
38+
})
39+
}
40+
}
41+
42+
override fun getAllLocalGroups(): Flow<List<Group>> {
43+
return flow {
44+
val data = suspendCoroutine { continuation ->
45+
dataManager.databaseGroups
46+
.observeOn(AndroidSchedulers.mainThread())
47+
.subscribeOn(Schedulers.io())
48+
.subscribe(object : Subscriber<Page<Group>>() {
49+
override fun onCompleted() {
50+
}
51+
52+
override fun onError(error: Throwable) {
53+
continuation.resumeWithException(error)
54+
}
55+
56+
override fun onNext(groups: Page<Group>) {
57+
continuation.resume(groups.pageItems)
58+
}
59+
})
60+
}
61+
62+
emit(data)
63+
}
64+
}
65+
}

core/database/src/main/java/com/mifos/core/databasehelper/DatabaseHelperGroups.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ class DatabaseHelperGroups @Inject constructor() {
7272
}
7373
}
7474

75+
/**
76+
* Reading All groups from Database table of Group and return the GroupList
77+
* @param offset
78+
* @param limit
79+
* @return List Of Groups
80+
*/
81+
fun readAllGroups(offset: Int, limit: Int): Observable<Page<Group>> {
82+
return Observable.defer {
83+
val groupPage = Page<Group>()
84+
groupPage.pageItems = SQLite.select()
85+
.from(Group::class.java)
86+
.offset(offset)
87+
.limit(limit)
88+
.queryList()
89+
Observable.just(groupPage)
90+
}
91+
}
92+
7593
/**
7694
* This Method Retrieving the Group from the Local Database.
7795
*

core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosProgressIndicator.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import androidx.compose.material3.CircularProgressIndicator
1111
import androidx.compose.runtime.Composable
1212
import androidx.compose.ui.Alignment
1313
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.platform.testTag
15+
import androidx.compose.ui.semantics.contentDescription
16+
import androidx.compose.ui.semantics.semantics
1417
import androidx.compose.ui.unit.dp
1518
import com.mifos.core.designsystem.theme.DarkGray
1619

@@ -38,13 +41,18 @@ fun MifosPagingAppendProgress() {
3841
}
3942

4043
@Composable
41-
fun MifosCircularProgress() {
44+
fun MifosCircularProgress(
45+
contentDesc: String = "loadingIndicator"
46+
) {
4247
Box(
43-
modifier = Modifier.fillMaxSize(),
48+
modifier = Modifier
49+
.fillMaxSize()
50+
.semantics { contentDescription = contentDesc },
4451
contentAlignment = Alignment.Center
4552
) {
4653
CircularProgressIndicator(
4754
modifier = Modifier
55+
.testTag("loadingWheel")
4856
.width(60.dp)
4957
.height(60.dp)
5058
.padding(8.dp),

core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosSweetError.kt

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@ import androidx.compose.foundation.layout.Column
66
import androidx.compose.foundation.layout.PaddingValues
77
import androidx.compose.foundation.layout.Spacer
88
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
910
import androidx.compose.foundation.layout.height
1011
import androidx.compose.foundation.layout.padding
1112
import androidx.compose.foundation.layout.size
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.Info
1215
import androidx.compose.material3.Button
1316
import androidx.compose.material3.ButtonDefaults
17+
import androidx.compose.material3.Icon
1418
import androidx.compose.material3.Text
1519
import androidx.compose.runtime.Composable
1620
import androidx.compose.ui.Alignment
1721
import androidx.compose.ui.Modifier
1822
import androidx.compose.ui.graphics.Color
1923
import androidx.compose.ui.graphics.ColorFilter
2024
import androidx.compose.ui.res.stringResource
25+
import androidx.compose.ui.semantics.contentDescription
26+
import androidx.compose.ui.semantics.semantics
2127
import androidx.compose.ui.text.TextStyle
2228
import androidx.compose.ui.text.font.FontStyle
2329
import androidx.compose.ui.text.font.FontWeight
@@ -34,7 +40,10 @@ fun MifosSweetError(message: String, onclick: () -> Unit) {
3440
Column(
3541
modifier = Modifier
3642
.fillMaxSize()
37-
.padding(18.dp),
43+
.padding(18.dp)
44+
.semantics {
45+
contentDescription = "MifosSweetError"
46+
},
3847
verticalArrangement = Arrangement.Center,
3948
horizontalAlignment = Alignment.CenterHorizontally
4049
) {
@@ -78,4 +87,48 @@ fun MifosSweetError(message: String, onclick: () -> Unit) {
7887
)
7988
}
8089
}
90+
}
91+
92+
93+
@Composable
94+
fun MifosPaginationSweetError(
95+
modifier: Modifier = Modifier,
96+
onclick: () -> Unit
97+
) {
98+
Column(
99+
modifier = modifier
100+
.fillMaxWidth()
101+
.padding(18.dp),
102+
verticalArrangement = Arrangement.spacedBy(4.dp),
103+
horizontalAlignment = Alignment.CenterHorizontally
104+
) {
105+
Icon(
106+
imageVector = Icons.Default.Info,
107+
contentDescription = "Info Image",
108+
tint = Color.Gray
109+
)
110+
Text(
111+
text = stringResource(id = R.string.core_designsystem_unable_to_load),
112+
style = TextStyle(
113+
fontSize = 14.sp,
114+
fontWeight = FontWeight.Normal,
115+
fontStyle = FontStyle.Normal,
116+
color = DarkGray
117+
)
118+
)
119+
Button(
120+
onClick = { onclick() },
121+
contentPadding = PaddingValues(),
122+
colors = ButtonDefaults.buttonColors(
123+
containerColor = if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary
124+
)
125+
) {
126+
Text(
127+
modifier = Modifier
128+
.padding(start = 20.dp, end = 20.dp),
129+
text = stringResource(id = R.string.core_designsystem_try_again),
130+
fontSize = 15.sp
131+
)
132+
}
133+
}
81134
}

core/domain/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ dependencies {
2727
implementation(libs.androidx.paging.runtime.ktx)
2828

2929
testImplementation(projects.core.testing)
30+
testImplementation (libs.androidx.paging.common.ktx)
31+
testImplementation (libs.androidx.paging.testing)
3032
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.mifos.core.domain.use_cases
2+
3+
import androidx.paging.PagingSource
4+
import androidx.paging.PagingState
5+
import com.mifos.core.data.repository.GroupsListRepository
6+
import com.mifos.core.objects.group.Group
7+
import retrofit2.HttpException
8+
import java.io.IOException
9+
10+
class GroupsListPagingDataSource(
11+
private val repository: GroupsListRepository,
12+
private val limit: Int,
13+
) : PagingSource<Int, Group>() {
14+
override fun getRefreshKey(state: PagingState<Int, Group>): Int? {
15+
return state.anchorPosition?.let { position ->
16+
state.closestPageToPosition(position)?.prevKey?.plus(limit)
17+
?: state.closestPageToPosition(
18+
position
19+
)?.nextKey?.minus(limit)
20+
}
21+
}
22+
23+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Group> {
24+
val currentOffset = params.key ?: 0
25+
return try {
26+
val groups = repository.getAllGroups(paged = true, currentOffset, limit)
27+
28+
LoadResult.Page(
29+
data = groups,
30+
prevKey = if (currentOffset <= 0) null else currentOffset - limit,
31+
nextKey = if (groups.isEmpty()) null else currentOffset + limit,
32+
)
33+
} catch (e: HttpException) {
34+
LoadResult.Error(e)
35+
} catch (e: IOException) {
36+
LoadResult.Error(e)
37+
} catch (e: Exception) {
38+
LoadResult.Error(e)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)