Skip to content

Commit 53dceda

Browse files
committed
Merge branch 'master' into feature-courses-viewmodel-orchestration-10508090116705126768
2 parents 55282c5 + 167d3ce commit 53dceda

9 files changed

Lines changed: 139 additions & 44 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId "org.ole.planet.myplanet"
1313
minSdk = 26
1414
targetSdk = 36
15-
versionCode = 5326
16-
versionName = "0.53.26"
15+
versionCode = 5331
16+
versionName = "0.53.31"
1717
ndkVersion = '26.3.11579264'
1818
vectorDrawables.useSupportLibrary = true
1919
}

app/src/main/java/org/ole/planet/myplanet/di/DatabaseModule.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import dagger.hilt.components.SingletonComponent
1111
import javax.inject.Singleton
1212
import kotlinx.coroutines.CoroutineDispatcher
1313
import kotlinx.coroutines.android.asCoroutineDispatcher
14-
import kotlinx.coroutines.asCoroutineDispatcher
1514
import org.ole.planet.myplanet.data.DatabaseService
1615
import org.ole.planet.myplanet.di.RealmDispatcher
1716
import org.ole.planet.myplanet.utils.DispatcherProvider
@@ -30,10 +29,9 @@ object DatabaseModule {
3029
@Singleton
3130
@RealmDispatcher
3231
fun provideRealmDispatcher(): CoroutineDispatcher {
33-
// App-level shutdown hook ownership
34-
return java.util.concurrent.Executors.newSingleThreadExecutor { r ->
35-
Thread(r, "RealmQueryThread")
36-
}.asCoroutineDispatcher()
32+
// Realm async queries and change listeners require a thread with a Looper.
33+
val handlerThread = HandlerThread("RealmQueryThread").also { it.start() }
34+
return Handler(handlerThread.looper).asCoroutineDispatcher()
3735
}
3836

3937
// Realm initialization is handled in DatabaseService
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.ole.planet.myplanet.model
2+
3+
data class TeamResourceDto(
4+
val resourceId: String,
5+
val title: String?
6+
)

app/src/main/java/org/ole/planet/myplanet/repository/TeamsRepository.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.ole.planet.myplanet.model.RealmMyTeam
1010
import org.ole.planet.myplanet.model.RealmTeamLog
1111
import org.ole.planet.myplanet.model.RealmTeamTask
1212
import org.ole.planet.myplanet.model.RealmUser
13+
import org.ole.planet.myplanet.model.TeamResourceDto
1314
import org.ole.planet.myplanet.model.TeamSummary
1415
import org.ole.planet.myplanet.model.Transaction
1516

@@ -72,7 +73,7 @@ interface TeamsRepository {
7273
suspend fun requestToJoin(teamId: String, userId: String?, userPlanetCode: String?, teamType: String?)
7374
suspend fun leaveTeam(teamId: String, userId: String?)
7475
suspend fun removeMember(teamId: String, userId: String)
75-
suspend fun addResourceLinks(teamId: String, resources: List<RealmMyLibrary>, user: RealmUser?)
76+
suspend fun addResourceLinks(teamId: String, resources: List<TeamResourceDto>, userId: String?)
7677
suspend fun removeResourceLink(teamId: String, resourceId: String)
7778
suspend fun deleteTask(taskId: String)
7879
suspend fun upsertTask(task: RealmTeamTask)

app/src/main/java/org/ole/planet/myplanet/repository/TeamsRepositoryImpl.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.ole.planet.myplanet.model.RealmMyTeam
3030
import org.ole.planet.myplanet.model.RealmTeamLog
3131
import org.ole.planet.myplanet.model.RealmTeamTask
3232
import org.ole.planet.myplanet.model.RealmUser
33+
import org.ole.planet.myplanet.model.TeamResourceDto
3334
import org.ole.planet.myplanet.model.TeamSummary
3435
import org.ole.planet.myplanet.model.Transaction
3536
import org.ole.planet.myplanet.model.User
@@ -689,18 +690,22 @@ class TeamsRepositoryImpl @Inject constructor(
689690

690691
override suspend fun addResourceLinks(
691692
teamId: String,
692-
resources: List<RealmMyLibrary>,
693-
user: RealmUser?,
693+
resources: List<TeamResourceDto>,
694+
userId: String?,
694695
) {
695-
if (teamId.isBlank() || resources.isEmpty() || user == null) return
696+
if (teamId.isBlank() || resources.isEmpty() || userId.isNullOrBlank()) return
697+
698+
val user = findByField(RealmUser::class.java, "id", userId)
699+
?: findByField(RealmUser::class.java, "_id", userId)
700+
?: return
696701

697702
val teamResources = resources.map { resource ->
698703
val teamResource = RealmMyTeam()
699704
teamResource._id = UUID.randomUUID().toString()
700705
teamResource.teamId = teamId
701706
teamResource.title = resource.title
702707
teamResource.status = user.parentCode
703-
teamResource.resourceId = resource._id
708+
teamResource.resourceId = resource.resourceId
704709
teamResource.docType = "resourceLink"
705710
teamResource.updated = true
706711
teamResource.teamType = "local"

app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import org.ole.planet.myplanet.utils.Utilities.toast
8989
class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, NavigationBarView.OnItemSelectedListener, OnNotificationsListener {
9090

9191
private var isReady = false
92+
private var isFirstLaunch = false
9293
private lateinit var binding: ActivityDashboardBinding
9394
private var headerResult: AccountHeader? = null
9495
var user: RealmUser? = null
@@ -141,7 +142,8 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N
141142
updateAppTitle()
142143
if (handleGuestAccess()) return
143144

144-
handleInitialFragment()
145+
isFirstLaunch = savedInstanceState == null
146+
if (isFirstLaunch) handleInitialFragment()
145147
addBackPressCallback()
146148
collectUiState()
147149

@@ -331,13 +333,15 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N
331333
dl = result?.drawerLayout
332334
topbarSetting()
333335

334-
lifecycleScope.launch {
335-
delay(50)
336-
val offlineVisits = userSessionManager.getOfflineVisits(user)
337-
if (!(user?.id?.startsWith("guest") == true && offlineVisits >= 3) &&
338-
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
339-
) {
340-
result?.openDrawer()
336+
if (isFirstLaunch) {
337+
lifecycleScope.launch {
338+
delay(50)
339+
val offlineVisits = userSessionManager.getOfflineVisits(user)
340+
if (!(user?.id?.startsWith("guest") == true && offlineVisits >= 3) &&
341+
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
342+
) {
343+
result?.openDrawer()
344+
}
341345
}
342346
}
343347
}

app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesAdapter.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ class ResourcesAdapter(
194194
}
195195
handled = true
196196
}
197+
if (payloads.contains(TAGS_PAYLOAD)) {
198+
displayTagCloud(holder, position)
199+
handled = true
200+
}
197201
if (!handled) {
198202
super.onBindViewHolder(holder, position, payloads)
199203
}

app/src/main/java/org/ole/planet/myplanet/ui/teams/resources/TeamResourcesFragment.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.ole.planet.myplanet.databinding.FragmentTeamResourceBinding
2727
import org.ole.planet.myplanet.databinding.MyLibraryAlertdialogBinding
2828
import org.ole.planet.myplanet.model.RealmMyLibrary
2929
import org.ole.planet.myplanet.model.RealmNews
30+
import org.ole.planet.myplanet.model.TeamResourceDto
3031
import org.ole.planet.myplanet.ui.components.CheckboxAdapter
3132
import org.ole.planet.myplanet.ui.resources.AddResourceFragment
3233

@@ -116,8 +117,12 @@ class TeamResourcesFragment : BaseTeamFragment(), OnTeamPageListener, OnResource
116117
.setPositiveButton(R.string.add) { _: DialogInterface?, _: Int ->
117118
val selectedResources = (myLibraryAlertdialogBinding.alertDialogListView.adapter as CheckboxAdapter).selectedItemsList
118119
.map { index -> availableLibraries[index] }
120+
.mapNotNull {
121+
val id = it._id
122+
if (id != null) TeamResourceDto(id, it.title) else null
123+
}
119124
viewLifecycleOwner.lifecycleScope.launch {
120-
teamsRepository.addResourceLinks(teamId, selectedResources, user)
125+
teamsRepository.addResourceLinks(teamId, selectedResources, user?.id)
121126
showLibraryList()
122127
}
123128
}

app/src/test/java/org/ole/planet/myplanet/services/NotificationActionReceiverTest.kt

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import android.content.Intent
55
import io.mockk.coVerify
66
import io.mockk.every
77
import io.mockk.mockk
8-
import io.mockk.mockkConstructor
98
import io.mockk.mockkStatic
9+
import io.mockk.spyk
10+
import io.mockk.verify
11+
import android.content.BroadcastReceiver
1012
import io.mockk.unmockkAll
11-
import io.mockk.unmockkConstructor
1213
import kotlinx.coroutines.CoroutineDispatcher
1314
import kotlinx.coroutines.ExperimentalCoroutinesApi
1415
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -23,16 +24,24 @@ import org.ole.planet.myplanet.di.getBroadcastService
2324
import org.ole.planet.myplanet.repository.NotificationsRepository
2425
import org.ole.planet.myplanet.utils.DispatcherProvider
2526
import org.ole.planet.myplanet.utils.NotificationUtils
26-
27+
import android.provider.Settings
28+
import org.junit.runner.RunWith
29+
import org.robolectric.RobolectricTestRunner
30+
import org.robolectric.annotation.Config
31+
import androidx.test.core.app.ApplicationProvider
32+
33+
@RunWith(RobolectricTestRunner::class)
34+
@Config(sdk = [33], application = android.app.Application::class)
2735
@OptIn(ExperimentalCoroutinesApi::class)
2836
class NotificationActionReceiverTest {
2937

3038
private lateinit var receiver: NotificationActionReceiver
3139
private lateinit var mockContext: Context
32-
private lateinit var mockNotificationsRepository: NotificationsRepository
3340
private lateinit var testDispatcher: CoroutineDispatcher
34-
private lateinit var mockDispatcherProvider: DispatcherProvider
3541
private lateinit var testScope: TestScope
42+
43+
private val mockNotificationsRepository: NotificationsRepository = mockk(relaxed = true)
44+
private var mockDispatcherProvider: DispatcherProvider = mockk(relaxed = true)
3645
private lateinit var mockNotificationUtils: NotificationUtils.NotificationManager
3746

3847
@Before
@@ -42,22 +51,13 @@ class NotificationActionReceiverTest {
4251

4352
MainApplication.applicationScope = testScope
4453

45-
mockContext = mockk<Context>(relaxed = true)
46-
mockNotificationsRepository = mockk(relaxed = true)
47-
48-
mockDispatcherProvider = object : DispatcherProvider {
49-
override val main: CoroutineDispatcher = testDispatcher
50-
override val io: CoroutineDispatcher = testDispatcher
51-
override val default: CoroutineDispatcher = testDispatcher
52-
override val unconfined: CoroutineDispatcher = testDispatcher
53-
}
54+
mockContext = spyk(ApplicationProvider.getApplicationContext<Context>())
55+
every { mockContext.startActivity(any()) } returns Unit
5456

55-
mockkConstructor(Intent::class)
56-
every { anyConstructed<Intent>().setPackage(any()) } answers { mockk() }
57-
every { anyConstructed<Intent>().putExtra(any<String>(), any<String>()) } answers { mockk() }
58-
every { anyConstructed<Intent>().putExtra(any<String>(), any<Boolean>()) } answers { mockk() }
59-
every { anyConstructed<Intent>().flags = any() } returns Unit
60-
every { anyConstructed<Intent>().action = any() } returns Unit
57+
every { mockDispatcherProvider.main } returns testDispatcher
58+
every { mockDispatcherProvider.io } returns testDispatcher
59+
every { mockDispatcherProvider.default } returns testDispatcher
60+
every { mockDispatcherProvider.unconfined } returns testDispatcher
6161

6262
mockNotificationUtils = mockk(relaxed = true)
6363
mockkStatic(NotificationUtils::class)
@@ -66,17 +66,22 @@ class NotificationActionReceiverTest {
6666
mockkStatic("org.ole.planet.myplanet.di.ServiceDependenciesEntryPointKt")
6767
every { getBroadcastService(any()) } returns mockk(relaxed = true)
6868

69-
70-
receiver = NotificationActionReceiver().apply {
69+
receiver = spyk(NotificationActionReceiver().apply {
7170
notificationsRepository = mockNotificationsRepository
7271
dispatcherProvider = mockDispatcherProvider
72+
})
73+
try {
74+
val injectedField = org.ole.planet.myplanet.services.Hilt_NotificationActionReceiver::class.java.getDeclaredField("injected")
75+
injectedField.isAccessible = true
76+
injectedField.set(receiver, true)
77+
} catch (e: Exception) {
78+
e.printStackTrace()
7379
}
7480
}
7581

7682
@After
7783
fun tearDown() {
7884
unmockkAll()
79-
unmockkConstructor(Intent::class)
8085
}
8186

8287
@Test
@@ -89,4 +94,71 @@ class NotificationActionReceiverTest {
8994

9095
coVerify { mockNotificationsRepository.markNotificationsAsRead(setOf(notificationId)) }
9196
}
97+
98+
@Test
99+
fun `test onReceive ACTION_MARK_AS_READ`() = testScope.runTest {
100+
val notificationId = "test_id"
101+
val mockIntent = Intent(NotificationUtils.ACTION_MARK_AS_READ)
102+
mockIntent.putExtra(NotificationUtils.EXTRA_NOTIFICATION_ID, notificationId)
103+
104+
val pendingResult = mockk<BroadcastReceiver.PendingResult>(relaxed = true)
105+
every { receiver.goAsync() } returns pendingResult
106+
107+
receiver.onReceive(mockContext, mockIntent)
108+
advanceUntilIdle()
109+
110+
coVerify { mockNotificationsRepository.markNotificationsAsRead(setOf(notificationId)) }
111+
verify { mockNotificationUtils.clearNotification(notificationId) }
112+
verify { pendingResult.finish() }
113+
}
114+
115+
@Test
116+
fun `test onReceive ACTION_STORAGE_SETTINGS`() = testScope.runTest {
117+
val notificationId = "test_id"
118+
val mockIntent = Intent(NotificationUtils.ACTION_STORAGE_SETTINGS)
119+
mockIntent.putExtra(NotificationUtils.EXTRA_NOTIFICATION_ID, notificationId)
120+
121+
val pendingResult = mockk<BroadcastReceiver.PendingResult>(relaxed = true)
122+
every { receiver.goAsync() } returns pendingResult
123+
124+
receiver.onReceive(mockContext, mockIntent)
125+
advanceUntilIdle()
126+
127+
coVerify { mockNotificationsRepository.markNotificationsAsRead(setOf(notificationId)) }
128+
val intentList = mutableListOf<Intent>()
129+
verify { mockContext.startActivity(capture(intentList)) }
130+
assert(intentList.any { it.action == Settings.ACTION_INTERNAL_STORAGE_SETTINGS })
131+
verify { mockNotificationUtils.clearNotification(notificationId) }
132+
verify { pendingResult.finish() }
133+
}
134+
135+
@Test
136+
fun `test onReceive ACTION_OPEN_NOTIFICATION`() = testScope.runTest {
137+
val notificationId = "test_id"
138+
val notificationType = "type"
139+
val relatedId = "related_id"
140+
141+
val mockIntent = Intent(NotificationUtils.ACTION_OPEN_NOTIFICATION)
142+
mockIntent.putExtra(NotificationUtils.EXTRA_NOTIFICATION_ID, notificationId)
143+
mockIntent.putExtra(NotificationUtils.EXTRA_NOTIFICATION_TYPE, notificationType)
144+
mockIntent.putExtra(NotificationUtils.EXTRA_RELATED_ID, relatedId)
145+
146+
val pendingResult = mockk<BroadcastReceiver.PendingResult>(relaxed = true)
147+
every { receiver.goAsync() } returns pendingResult
148+
149+
receiver.onReceive(mockContext, mockIntent)
150+
advanceUntilIdle()
151+
152+
coVerify { mockNotificationsRepository.markNotificationsAsRead(setOf(notificationId)) }
153+
val intentList = mutableListOf<Intent>()
154+
verify { mockContext.startActivity(capture(intentList)) }
155+
val targetIntent = intentList.find { it.getStringExtra("notification_type") == notificationType }
156+
assert(targetIntent != null)
157+
assert(targetIntent?.getStringExtra("notification_type") == notificationType)
158+
assert(targetIntent?.getStringExtra("notification_id") == notificationId)
159+
assert(targetIntent?.getStringExtra("related_id") == relatedId)
160+
161+
verify { mockNotificationUtils.clearNotification(notificationId) }
162+
verify { pendingResult.finish() }
163+
}
92164
}

0 commit comments

Comments
 (0)