From 47f3f3b2e6971ed10a62b864db2889716f39e998 Mon Sep 17 00:00:00 2001 From: Hadi Satrio <hi@hadisatrio.com> Date: Tue, 31 Dec 2024 18:21:48 +0700 Subject: [PATCH 1/6] kotlin/journal3: Mark user-committed moments as notable Ensure that moments explicitly created or edited by the user are marked as notable. --- .../hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCase.kt | 1 + .../apps/kotlin/journal3/moment/EditAMomentUseCaseTest.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCase.kt b/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCase.kt index 74ce8600..c7fd6235 100644 --- a/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCase.kt +++ b/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCase.kt @@ -111,6 +111,7 @@ class EditAMomentUseCase( } private fun handleCommitActionSelection() { + moment.update(isNotable = true) if (isParaphrasingEnabled) { DescriptionParaphrasingMoment(paraphraser, moment).commit() } else { diff --git a/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCaseTest.kt b/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCaseTest.kt index 8d864e8a..eb3ddd18 100644 --- a/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCaseTest.kt +++ b/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/EditAMomentUseCaseTest.kt @@ -83,6 +83,7 @@ class EditAMomentUseCaseTest { moment.sentiment.shouldBe(Sentiment(0.75F)) moment.place.id.shouldBe(place.id) moment.attachments.shouldHaveSize(2) + moment.isNotable.shouldBeTrue() } @Test From 98704e9fa83dc7ddc97c5877b5270b151ad16a98 Mon Sep 17 00:00:00 2001 From: Hadi Satrio <hi@hadisatrio.com> Date: Tue, 31 Dec 2024 18:25:27 +0700 Subject: [PATCH 2/6] kotlin/journal3: Model a notability-filtering story Introduce a new type of story that filters its moments based on their notability. --- .../moment/NotabilityFilteringMoments.kt | 42 +++++++++++++++++++ .../story/NotabilityFilteringStory.kt | 31 ++++++++++++++ .../story/NotabilityFilteringStoryTest.kt | 38 +++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/NotabilityFilteringMoments.kt create mode 100644 app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStory.kt create mode 100644 app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStoryTest.kt diff --git a/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/NotabilityFilteringMoments.kt b/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/NotabilityFilteringMoments.kt new file mode 100644 index 00000000..a49f3234 --- /dev/null +++ b/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/NotabilityFilteringMoments.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Hadi Satrio + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.hadisatrio.apps.kotlin.journal3.moment + +import com.benasher44.uuid.Uuid + +class NotabilityFilteringMoments( + private val notable: Boolean, + private val origin: Moments +) : Moments { + + override fun count(): Int { + return toList().size + } + + override fun find(id: Uuid): Iterable<Moment> { + return filter { it.id == id } + } + + override fun mostRecent(): Moment { + return maxBy { it.timestamp } + } + + override fun iterator(): Iterator<Moment> { + return origin.asSequence().filter { it.isNotable == notable }.iterator() + } +} diff --git a/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStory.kt b/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStory.kt new file mode 100644 index 00000000..9414949c --- /dev/null +++ b/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStory.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Hadi Satrio + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.hadisatrio.apps.kotlin.journal3.story + +import com.hadisatrio.apps.kotlin.journal3.moment.Moments +import com.hadisatrio.apps.kotlin.journal3.moment.NotabilityFilteringMoments + +class NotabilityFilteringStory( + private val notable: Boolean, + private val origin: Story +) : Story by origin { + + override val moments: Moments get() { + return NotabilityFilteringMoments(notable, origin.moments) + } +} diff --git a/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStoryTest.kt b/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStoryTest.kt new file mode 100644 index 00000000..9db39e82 --- /dev/null +++ b/app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/story/NotabilityFilteringStoryTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Hadi Satrio + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.hadisatrio.apps.kotlin.journal3.story + +import com.hadisatrio.apps.kotlin.journal3.moment.EditableMoment +import com.hadisatrio.apps.kotlin.journal3.story.fake.FakeStory +import io.kotest.matchers.shouldBe +import kotlin.test.Test + +class NotabilityFilteringStoryTest { + + @Test + fun `Filters moments on basis of their notability`() { + val story = SelfPopulatingStory(noOfMoments = 10, FakeStory()) + (story.moments.first() as EditableMoment).update(isNotable = true) + + val notableCount = NotabilityFilteringStory(notable = true, story).moments.count() + val nonNotableCount = NotabilityFilteringStory(notable = false, story).moments.count() + + notableCount.shouldBe(1) + nonNotableCount.shouldBe(9) + } +} From 8cd29e07b376b730147f5815536968e04f53dd6f Mon Sep 17 00:00:00 2001 From: Hadi Satrio <hi@hadisatrio.com> Date: Tue, 31 Dec 2024 18:35:55 +0700 Subject: [PATCH 3/6] android/journal3: Hide non-notable moments from the UI Non-notable moments are those that are automatically generated by the application, rather than being explicitly created or edited by the user. These moments are now hidden from the main list view and other relevant UI elements. --- .../apps/android/journal3/Journal3Application.kt | 1 + .../android/journal3/RealJournal3Application.kt | 13 +++++++++---- .../android/journal3/story/ViewStoryFragment.kt | 2 +- .../android/journal3/FakeJournal3Application.kt | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/Journal3Application.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/Journal3Application.kt index 2ec3f450..2284e85c 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/Journal3Application.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/Journal3Application.kt @@ -46,6 +46,7 @@ abstract class Journal3Application : Application() { abstract val speed: Speed abstract val places: Places abstract val story: Story + abstract val notableStory: Story abstract val reflections: Stories abstract val modalPresenter: Presenter<Modal> abstract val currentActivity: CurrentActivity diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RealJournal3Application.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RealJournal3Application.kt index d79e52c8..6baaeb50 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RealJournal3Application.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RealJournal3Application.kt @@ -42,6 +42,7 @@ import com.hadisatrio.apps.kotlin.journal3.sentiment.SentimentAnalyst import com.hadisatrio.apps.kotlin.journal3.sentiment.VeryPositiveSentimentRange import com.hadisatrio.apps.kotlin.journal3.story.InitDeferringStories import com.hadisatrio.apps.kotlin.journal3.story.MomentfulStories +import com.hadisatrio.apps.kotlin.journal3.story.NotabilityFilteringStory import com.hadisatrio.apps.kotlin.journal3.story.Reflection import com.hadisatrio.apps.kotlin.journal3.story.Stories import com.hadisatrio.apps.kotlin.journal3.story.Story @@ -111,6 +112,10 @@ class RealJournal3Application : Journal3Application() { ) } + override val notableStory: Story by lazy { + NotabilityFilteringStory(notable = true, story) + } + private val memorables by lazy { MergedMemorables( FilesystemMemorablePlaces( @@ -145,7 +150,7 @@ class RealJournal3Application : Journal3Application() { origin = VicinityMoments( coordinates = coordinates, distanceLimitInM = 100.0, - origin = story.moments + origin = notableStory.moments ) ) ) @@ -162,7 +167,7 @@ class RealJournal3Application : Journal3Application() { ), origin = SentimentRangedMoments( sentimentRange = PositiveishSentimentRange, - origin = story.moments + origin = notableStory.moments ) ) ) @@ -176,7 +181,7 @@ class RealJournal3Application : Journal3Application() { origin = OrderRandomizingMoments( origin = SentimentRangedMoments( sentimentRange = VeryPositiveSentimentRange, - origin = story.moments + origin = notableStory.moments ) ) ) @@ -189,7 +194,7 @@ class RealJournal3Application : Journal3Application() { origin = OrderRandomizingMoments( origin = SentimentRangedMoments( sentimentRange = NegativeishSentimentRange, - origin = story.moments + origin = notableStory.moments ) ) ) diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt index ed4c2d96..1bd0bc5f 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt @@ -125,7 +125,7 @@ class ViewStoryFragment : Fragment() { private val useCase: UseCase by lazy { journal3Application.useCaseDecor.apply( ShowStoryUseCase( - story = journal3Application.story, + story = journal3Application.notableStory, presenter = presenter, eventSource = eventSource, eventSink = eventSink diff --git a/app-android-journal3/src/test/kotlin/com/hadisatrio/apps/android/journal3/FakeJournal3Application.kt b/app-android-journal3/src/test/kotlin/com/hadisatrio/apps/android/journal3/FakeJournal3Application.kt index e7d6ce1a..f56b02c5 100644 --- a/app-android-journal3/src/test/kotlin/com/hadisatrio/apps/android/journal3/FakeJournal3Application.kt +++ b/app-android-journal3/src/test/kotlin/com/hadisatrio/apps/android/journal3/FakeJournal3Application.kt @@ -22,6 +22,7 @@ import com.badoo.reaktive.subject.publish.PublishSubject import com.hadisatrio.apps.kotlin.journal3.datetime.Timestamp import com.hadisatrio.apps.kotlin.journal3.sentiment.DumbSentimentAnalyst import com.hadisatrio.apps.kotlin.journal3.sentiment.SentimentAnalyst +import com.hadisatrio.apps.kotlin.journal3.story.NotabilityFilteringStory import com.hadisatrio.apps.kotlin.journal3.story.SelfPopulatingStory import com.hadisatrio.apps.kotlin.journal3.story.Stories import com.hadisatrio.apps.kotlin.journal3.story.Story @@ -58,6 +59,7 @@ class FakeJournal3Application : Journal3Application() { override val speed: Speed by lazy { StaticSpeed(5.0) } override val places: Places by lazy { SelfPopulatingPlaces(10, FakePlaces()) } override val story: Story by lazy { SelfPopulatingStory(10, FakeStory()) } + override val notableStory: Story by lazy { NotabilityFilteringStory(notable = true, story) } override val reflections: Stories by lazy { FakeStories() } override val modalPresenter: Presenter<Modal> by lazy { FakePresenter() } override val currentActivity: CurrentActivity by lazy { CurrentActivity(this) } From 9f61692e45423e2cd8604fcb43a06deae3bc6919 Mon Sep 17 00:00:00 2001 From: Hadi Satrio <hi@hadisatrio.com> Date: Tue, 31 Dec 2024 19:02:02 +0700 Subject: [PATCH 4/6] android/journal3: Implement writing suggestions screen Define a new activity to show a list of non-notable moments as writing suggestions for the user and wire it to the root activity. --- .../src/main/AndroidManifest.xml | 4 + .../journal3/ActivityRoutingEventSink.kt | 7 + .../apps/android/journal3/RootActivity.kt | 2 +- .../moment/ViewWritingSuggestionsActivity.kt | 178 ++++++++++++++++++ .../fragment_view_writing_suggestions.xml | 37 ++++ .../src/main/res/values/themes.xml | 1 + 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt create mode 100644 app-android-journal3/src/main/res/layout/fragment_view_writing_suggestions.xml diff --git a/app-android-journal3/src/main/AndroidManifest.xml b/app-android-journal3/src/main/AndroidManifest.xml index 6bcc0f26..c2f9bcbb 100644 --- a/app-android-journal3/src/main/AndroidManifest.xml +++ b/app-android-journal3/src/main/AndroidManifest.xml @@ -101,6 +101,10 @@ </intent-filter> </activity> + <activity + android:name=".moment.ViewWritingSuggestionsActivity" + android:theme="@style/Theme.Journal3.Translucent" /> + <activity android:name=".moment.EditAMomentActivity" android:theme="@style/Theme.Journal3.NoActionBar" /> diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/ActivityRoutingEventSink.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/ActivityRoutingEventSink.kt index 34476459..e1547bec 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/ActivityRoutingEventSink.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/ActivityRoutingEventSink.kt @@ -22,6 +22,7 @@ import android.content.Intent import com.hadisatrio.apps.android.journal3.geography.SelectAPlaceActivity import com.hadisatrio.apps.android.journal3.moment.DeleteAMomentActivity import com.hadisatrio.apps.android.journal3.moment.EditAMomentActivity +import com.hadisatrio.apps.android.journal3.moment.ViewWritingSuggestionsActivity import com.hadisatrio.libs.android.foundation.activity.CurrentActivity import com.hadisatrio.libs.kotlin.foundation.event.Event import com.hadisatrio.libs.kotlin.foundation.event.EventSink @@ -38,6 +39,7 @@ class ActivityRoutingEventSink( when (identifier) { "view_reflections" -> activity.startViewReflectionsActivity() "view_story" -> activity.startViewStoryActivity() + "view_writing_suggestions" -> activity.startViewWritingSuggestionsActivity() "add_moment" -> activity.startAddAMomentActivity(event) "edit_moment" -> activity.startEditAMomentActivity(event) "delete_moment" -> activity.startDeleteAMomentActivity(event) @@ -57,6 +59,11 @@ class ActivityRoutingEventSink( startActivity(intent) } + private fun Activity.startViewWritingSuggestionsActivity() { + val intent = Intent(this, ViewWritingSuggestionsActivity::class.java) + startActivity(intent) + } + private fun Activity.startAddAMomentActivity(event: SelectionEvent) { val intent = Intent(this, EditAMomentActivity::class.java) intent.putExtra("story_id", event["story_id"]) diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RootActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RootActivity.kt index 5499262c..d58b3ac5 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RootActivity.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RootActivity.kt @@ -59,7 +59,7 @@ class RootActivity : AppCompatActivity() { ), ViewClickEventSource( view = findViewById(R.id.add_button), - eventFactory = { SelectionEvent("action", "add_moment") } + eventFactory = { SelectionEvent("action", "view_writing_suggestions") } ), NavigationBarSelectionEventSource( view = bottomBar, diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt new file mode 100644 index 00000000..6ca503de --- /dev/null +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2022 Hadi Satrio + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.hadisatrio.apps.android.journal3.moment + +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.grzegorzojdana.spacingitemdecoration.Spacing +import com.grzegorzojdana.spacingitemdecoration.SpacingItemDecoration +import com.hadisatrio.apps.android.journal3.R +import com.hadisatrio.apps.android.journal3.journal3Application +import com.hadisatrio.apps.android.journal3.sentiment.TextViewColorSentimentPresenter +import com.hadisatrio.apps.kotlin.journal3.event.RefreshRequestEvent +import com.hadisatrio.apps.kotlin.journal3.moment.Moment +import com.hadisatrio.apps.kotlin.journal3.story.NotabilityFilteringStory +import com.hadisatrio.apps.kotlin.journal3.story.ShowStoryUseCase +import com.hadisatrio.apps.kotlin.journal3.story.Story +import com.hadisatrio.apps.kotlin.journal3.story.cache.CachingStoryPresenter +import com.hadisatrio.libs.android.dimensions.dp +import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.lifecycle.LifecycleTriggeredEventSource +import com.hadisatrio.libs.android.foundation.presentation.ExecutorDispatchingPresenter +import com.hadisatrio.libs.android.foundation.widget.ViewClickEventSource +import com.hadisatrio.libs.android.foundation.widget.recyclerview.ListViewPresenter +import com.hadisatrio.libs.android.foundation.widget.recyclerview.RecyclerViewItemSelectionEventSource +import com.hadisatrio.libs.android.foundation.widget.recyclerview.ViewFactory +import com.hadisatrio.libs.kotlin.foundation.UseCase +import com.hadisatrio.libs.kotlin.foundation.event.CancellationEvent +import com.hadisatrio.libs.kotlin.foundation.event.EventSink +import com.hadisatrio.libs.kotlin.foundation.event.EventSinks +import com.hadisatrio.libs.kotlin.foundation.event.EventSource +import com.hadisatrio.libs.kotlin.foundation.event.EventSources +import com.hadisatrio.libs.kotlin.foundation.event.SelectionEvent +import com.hadisatrio.libs.kotlin.foundation.event.SkippingEventSource +import com.hadisatrio.libs.kotlin.foundation.presentation.AdaptingPresenter +import com.hadisatrio.libs.kotlin.foundation.presentation.Presenter + +class ViewWritingSuggestionsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Fragment().show(supportFragmentManager, FRAGMENT_TAG) + } + + class Fragment : BottomSheetDialogFragment() { + + private val presenter: Presenter<Story> by lazy { + val momentsViewFactory = ViewFactory { parent, _ -> + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.view_moment_horz_card, parent, false) + val width = RecyclerView.LayoutParams.MATCH_PARENT + val height = RecyclerView.LayoutParams.WRAP_CONTENT + val sentimentPresenter = TextViewColorSentimentPresenter(view.findViewById(R.id.sentiment_indicator)) + view.layoutParams = RecyclerView.LayoutParams(width, height) + view.setTag(R.id.presenter_view_tag, sentimentPresenter) + view + } + val momentsPresenter = AdaptingPresenter<Story, Iterable<Moment>>( + adapter = { story -> story.moments }, + origin = ListViewPresenter( + recyclerView = requireView().findViewById(R.id.moments_list), + orientation = RecyclerView.VERTICAL, + viewFactory = momentsViewFactory, + viewRenderer = MomentCardViewRenderer, + differ = MomentItemDiffer, + backgroundExecutor = journal3Application.backgroundExecutor + ) + ) + + journal3Application.presenterDecor<Story>().apply( + CachingStoryPresenter( + origin = ExecutorDispatchingPresenter( + executor = journal3Application.foregroundExecutor, + origin = momentsPresenter + ) + ) + ) + } + + private val eventSource: EventSource by lazy { + journal3Application.eventSourceDecor.apply( + EventSources( + journal3Application.globalEventSource, + SkippingEventSource( + count = 1, + origin = LifecycleTriggeredEventSource( + lifecycleOwner = this, + lifecycleEvent = Lifecycle.Event.ON_START, + eventFactory = { RefreshRequestEvent("lifecycle") } + ) + ), + LifecycleTriggeredEventSource( + lifecycleOwner = this, + lifecycleEvent = Lifecycle.Event.ON_DESTROY, + eventFactory = { CancellationEvent("system") } + ), + ViewClickEventSource( + view = requireView().findViewById(R.id.add_button), + eventFactory = { SelectionEvent("action", "add_moment") } + ), + RecyclerViewItemSelectionEventSource( + recyclerView = requireView().findViewById(R.id.moments_list) + ) + ) + ) + } + + private val eventSink: EventSink by lazy { + journal3Application.eventSinkDecor.apply( + EventSinks( + journal3Application.globalEventSink, + ActivityCompletionEventSink(requireActivity()) + ) + ) + } + + private val useCase: UseCase by lazy { + journal3Application.useCaseDecor.apply( + ShowStoryUseCase( + story = NotabilityFilteringStory(notable = false, journal3Application.story), + presenter = presenter, + eventSource = eventSource, + eventSink = eventSink + ) + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_view_writing_suggestions, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setupViews() + useCase() + } + + private fun setupViews() { + requireView().findViewById<RecyclerView>(R.id.moments_list).addItemDecoration( + SpacingItemDecoration( + Spacing( + edges = Rect(0.dp, 0.dp, 0.dp, 0.dp), + horizontal = 0.dp, + vertical = 8.dp + ) + ) + ) + } + } + + companion object { + private val FRAGMENT_TAG = "${ViewWritingSuggestionsActivity::class.simpleName}Fragment" + } +} diff --git a/app-android-journal3/src/main/res/layout/fragment_view_writing_suggestions.xml b/app-android-journal3/src/main/res/layout/fragment_view_writing_suggestions.xml new file mode 100644 index 00000000..3be0e107 --- /dev/null +++ b/app-android-journal3/src/main/res/layout/fragment_view_writing_suggestions.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 Hadi Satrio + ~ + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/margin"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/add_button" + style="@style/Widget.Material3.Button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="New moment" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/moments_list" + android:layout_marginTop="@dimen/margin" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/app-android-journal3/src/main/res/values/themes.xml b/app-android-journal3/src/main/res/values/themes.xml index 50b9b727..067b3d35 100644 --- a/app-android-journal3/src/main/res/values/themes.xml +++ b/app-android-journal3/src/main/res/values/themes.xml @@ -28,6 +28,7 @@ </style> <style name="Theme.Journal3.Translucent" parent="Theme.Journal3"> + <item name="windowNoTitle">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> From 71fd0b983de5c25248248488c7fcd4dc6cdec857 Mon Sep 17 00:00:00 2001 From: Hadi Satrio <hi@hadisatrio.com> Date: Tue, 31 Dec 2024 21:58:41 +0700 Subject: [PATCH 5/6] android/foundation: Expand activity-finishing sink to accept predicates Expand the activity-finishing sink to cover more requirements by allowing it to accept an optional predicate. --- .../journal3/geography/SelectAPlaceActivity.kt | 4 ++-- .../journal3/moment/DeleteAMomentActivity.kt | 4 ++-- .../android/journal3/moment/EditAMomentActivity.kt | 4 ++-- .../moment/ViewWritingSuggestionsActivity.kt | 4 ++-- .../android/journal3/story/StoriesListFragment.kt | 4 ++-- .../android/journal3/story/ViewStoryFragment.kt | 4 ++-- ...onEventSink.kt => ActivityFinishingEventSink.kt} | 8 +++++--- ...inkTest.kt => ActivityFinishingEventSinkTest.kt} | 13 +++++++++++-- 8 files changed, 28 insertions(+), 17 deletions(-) rename lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/{ActivityCompletionEventSink.kt => ActivityFinishingEventSink.kt} (79%) rename lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/{ActivityCompletionEventSinkTest.kt => ActivityFinishingEventSinkTest.kt} (83%) diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/geography/SelectAPlaceActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/geography/SelectAPlaceActivity.kt index 83828794..b9c0f9e1 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/geography/SelectAPlaceActivity.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/geography/SelectAPlaceActivity.kt @@ -30,7 +30,7 @@ import com.hadisatrio.apps.android.journal3.R import com.hadisatrio.apps.android.journal3.journal3Application import com.hadisatrio.apps.kotlin.journal3.geography.SelectAPlaceUseCase import com.hadisatrio.libs.android.dimensions.dp -import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.activity.ActivityFinishingEventSink import com.hadisatrio.libs.android.foundation.activity.ActivityResultSettingEventSink import com.hadisatrio.libs.android.foundation.lifecycle.LifecycleTriggeredEventSource import com.hadisatrio.libs.android.foundation.presentation.ExecutorDispatchingPresenter @@ -116,7 +116,7 @@ class SelectAPlaceActivity : AppCompatActivity() { values } ), - ActivityCompletionEventSink(this) + ActivityFinishingEventSink(this) ) ) } diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/DeleteAMomentActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/DeleteAMomentActivity.kt index 8a7f0c34..c33dbc5a 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/DeleteAMomentActivity.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/DeleteAMomentActivity.kt @@ -22,7 +22,7 @@ import androidx.appcompat.app.AppCompatActivity import com.benasher44.uuid.uuidFrom import com.hadisatrio.apps.android.journal3.journal3Application import com.hadisatrio.apps.kotlin.journal3.moment.DeleteMomentUseCase -import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.activity.ActivityFinishingEventSink import com.hadisatrio.libs.android.foundation.presentation.ExecutorDispatchingPresenter import com.hadisatrio.libs.kotlin.foundation.event.EventSinks import com.hadisatrio.libs.kotlin.foundation.modal.Modal @@ -48,7 +48,7 @@ class DeleteAMomentActivity : AppCompatActivity() { eventSink = journal3Application.eventSinkDecor.apply( EventSinks( journal3Application.globalEventSink, - ActivityCompletionEventSink(this) + ActivityFinishingEventSink(this) ) ) ) diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/EditAMomentActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/EditAMomentActivity.kt index e74315b3..e9134f1d 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/EditAMomentActivity.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/EditAMomentActivity.kt @@ -37,7 +37,7 @@ import com.hadisatrio.apps.kotlin.journal3.moment.Moment import com.hadisatrio.apps.kotlin.journal3.moment.SentimentAnalyzingMoment import com.hadisatrio.apps.kotlin.journal3.moment.UpdateDeferringMoment import com.hadisatrio.apps.kotlin.journal3.story.EditableMomentInStory -import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.activity.ActivityFinishingEventSink import com.hadisatrio.libs.android.foundation.lifecycle.LifecycleTriggeredEventSource import com.hadisatrio.libs.android.foundation.material.SliderFloatPresenter import com.hadisatrio.libs.android.foundation.material.SliderSelectionEventSource @@ -179,7 +179,7 @@ class EditAMomentActivity : AppCompatActivity() { journal3Application.eventSinkDecor.apply( EventSinks( journal3Application.globalEventSink, - ActivityCompletionEventSink(this) + ActivityFinishingEventSink(this) ) ) } diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt index 6ca503de..6f6b4152 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt @@ -38,7 +38,7 @@ import com.hadisatrio.apps.kotlin.journal3.story.ShowStoryUseCase import com.hadisatrio.apps.kotlin.journal3.story.Story import com.hadisatrio.apps.kotlin.journal3.story.cache.CachingStoryPresenter import com.hadisatrio.libs.android.dimensions.dp -import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.activity.ActivityFinishingEventSink import com.hadisatrio.libs.android.foundation.lifecycle.LifecycleTriggeredEventSource import com.hadisatrio.libs.android.foundation.presentation.ExecutorDispatchingPresenter import com.hadisatrio.libs.android.foundation.widget.ViewClickEventSource @@ -130,7 +130,7 @@ class ViewWritingSuggestionsActivity : AppCompatActivity() { journal3Application.eventSinkDecor.apply( EventSinks( journal3Application.globalEventSink, - ActivityCompletionEventSink(requireActivity()) + ActivityFinishingEventSink(requireActivity()) ) ) } diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/StoriesListFragment.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/StoriesListFragment.kt index 43a700b5..4048f085 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/StoriesListFragment.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/StoriesListFragment.kt @@ -31,7 +31,7 @@ import com.hadisatrio.apps.android.journal3.journal3Application import com.hadisatrio.apps.kotlin.journal3.story.ShowStoriesUseCase import com.hadisatrio.apps.kotlin.journal3.story.Stories import com.hadisatrio.libs.android.dimensions.dp -import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.activity.ActivityFinishingEventSink import com.hadisatrio.libs.kotlin.foundation.UseCase import com.hadisatrio.libs.kotlin.foundation.event.EventSink import com.hadisatrio.libs.kotlin.foundation.event.EventSinks @@ -54,7 +54,7 @@ abstract class StoriesListFragment : Fragment() { journal3Application.eventSinkDecor.apply( EventSinks( journal3Application.globalEventSink, - ActivityCompletionEventSink(requireActivity()) + ActivityFinishingEventSink(requireActivity()) ) ) } diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt index 1bd0bc5f..0fb5478b 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoryFragment.kt @@ -38,7 +38,7 @@ import com.hadisatrio.apps.kotlin.journal3.story.ShowStoryUseCase import com.hadisatrio.apps.kotlin.journal3.story.Story import com.hadisatrio.apps.kotlin.journal3.story.cache.CachingStoryPresenter import com.hadisatrio.libs.android.dimensions.dp -import com.hadisatrio.libs.android.foundation.activity.ActivityCompletionEventSink +import com.hadisatrio.libs.android.foundation.activity.ActivityFinishingEventSink import com.hadisatrio.libs.android.foundation.lifecycle.LifecycleTriggeredEventSource import com.hadisatrio.libs.android.foundation.presentation.ExecutorDispatchingPresenter import com.hadisatrio.libs.android.foundation.widget.recyclerview.ListViewPresenter @@ -117,7 +117,7 @@ class ViewStoryFragment : Fragment() { journal3Application.eventSinkDecor.apply( EventSinks( journal3Application.globalEventSink, - ActivityCompletionEventSink(requireActivity()) + ActivityFinishingEventSink(requireActivity()) ) ) } diff --git a/lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityCompletionEventSink.kt b/lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityFinishingEventSink.kt similarity index 79% rename from lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityCompletionEventSink.kt rename to lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityFinishingEventSink.kt index b008622a..81566cb1 100644 --- a/lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityCompletionEventSink.kt +++ b/lib-kmm-foundation/src/androidMain/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityFinishingEventSink.kt @@ -21,13 +21,15 @@ import android.app.Activity import com.hadisatrio.libs.kotlin.foundation.event.CompletionEvent import com.hadisatrio.libs.kotlin.foundation.event.Event import com.hadisatrio.libs.kotlin.foundation.event.EventSink +import com.hadisatrio.libs.kotlin.foundation.event.Predicate -class ActivityCompletionEventSink( - private val activity: Activity +class ActivityFinishingEventSink( + private val activity: Activity, + private val additionalPredicate: Predicate<Event> = Predicate { false } ) : EventSink { override fun sink(event: Event) { - if (event !is CompletionEvent) return + if (event !is CompletionEvent && !additionalPredicate.applicable(event)) return if (activity.isFinishing || activity.isChangingConfigurations) return activity.finish() } diff --git a/lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityCompletionEventSinkTest.kt b/lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityFinishingEventSinkTest.kt similarity index 83% rename from lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityCompletionEventSinkTest.kt rename to lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityFinishingEventSinkTest.kt index c225478d..2a98ab4b 100644 --- a/lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityCompletionEventSinkTest.kt +++ b/lib-kmm-foundation/src/androidUnitTest/kotlin/com/hadisatrio/libs/android/foundation/activity/ActivityFinishingEventSinkTest.kt @@ -21,8 +21,10 @@ import androidx.activity.ComponentActivity import androidx.test.runner.AndroidJUnit4 import com.hadisatrio.libs.kotlin.foundation.event.CancellationEvent import com.hadisatrio.libs.kotlin.foundation.event.CompletionEvent +import com.hadisatrio.libs.kotlin.foundation.event.Event import com.hadisatrio.libs.kotlin.foundation.event.SelectionEvent import com.hadisatrio.libs.kotlin.foundation.event.TextInputEvent +import com.hadisatrio.libs.kotlin.foundation.event.fake.FakeEvent import com.hadisatrio.libs.kotlin.foundation.modal.ModalApprovalEvent import io.mockk.every import io.mockk.spyk @@ -33,11 +35,12 @@ import org.junit.runner.RunWith import org.robolectric.Robolectric @RunWith(AndroidJUnit4::class) -class ActivityCompletionEventSinkTest { +class ActivityFinishingEventSinkTest { private val activityController = Robolectric.buildActivity(ComponentActivity::class.java) private val activity = spyk(activityController.get()) - private val eventSink = ActivityCompletionEventSink(activity) + private val additionalPredicate = { event: Event -> event is FakeEvent } + private val eventSink = ActivityFinishingEventSink(activity, additionalPredicate) @Before fun `Starts activity`() { @@ -50,6 +53,12 @@ class ActivityCompletionEventSinkTest { verify(exactly = 1) { activity.finish() } } + @Test + fun `Finishes the activity upon receiving an event matching the given predicate`() { + eventSink.sink(FakeEvent()) + verify(exactly = 1) { activity.finish() } + } + @Test fun `Does not try to finish if the activity is already finishing`() { activity.finish() From 51e9ad165996bf109faa946503f8d882eee6ea6d Mon Sep 17 00:00:00 2001 From: Hadi Satrio <hi@hadisatrio.com> Date: Tue, 31 Dec 2024 22:02:21 +0700 Subject: [PATCH 6/6] android/journal3: Close writing suggestions screen on appropriate actions Configure the writing suggestions screen to close (or finish, as it is an activity) itself when the user has chosen to either add a new moment or edit one of the suggestions shown. --- .../moment/ViewWritingSuggestionsActivity.kt | 6 ++- .../event/ActionSelectionEventPredicate.kt | 32 ++++++++++++++++ .../ActionSelectionEventPredicateTest.kt | 37 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 lib-kmm-foundation/src/commonMain/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicate.kt create mode 100644 lib-kmm-foundation/src/commonTest/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicateTest.kt diff --git a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt index 6f6b4152..1aa9c2a3 100644 --- a/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt +++ b/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/ViewWritingSuggestionsActivity.kt @@ -46,6 +46,7 @@ import com.hadisatrio.libs.android.foundation.widget.recyclerview.ListViewPresen import com.hadisatrio.libs.android.foundation.widget.recyclerview.RecyclerViewItemSelectionEventSource import com.hadisatrio.libs.android.foundation.widget.recyclerview.ViewFactory import com.hadisatrio.libs.kotlin.foundation.UseCase +import com.hadisatrio.libs.kotlin.foundation.event.ActionSelectionEventPredicate import com.hadisatrio.libs.kotlin.foundation.event.CancellationEvent import com.hadisatrio.libs.kotlin.foundation.event.EventSink import com.hadisatrio.libs.kotlin.foundation.event.EventSinks @@ -130,7 +131,10 @@ class ViewWritingSuggestionsActivity : AppCompatActivity() { journal3Application.eventSinkDecor.apply( EventSinks( journal3Application.globalEventSink, - ActivityFinishingEventSink(requireActivity()) + ActivityFinishingEventSink( + activity = requireActivity(), + additionalPredicate = ActionSelectionEventPredicate("edit_moment", "add_moment") + ) ) ) } diff --git a/lib-kmm-foundation/src/commonMain/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicate.kt b/lib-kmm-foundation/src/commonMain/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicate.kt new file mode 100644 index 00000000..4e5c37d8 --- /dev/null +++ b/lib-kmm-foundation/src/commonMain/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicate.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 Hadi Satrio + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.hadisatrio.libs.kotlin.foundation.event + +class ActionSelectionEventPredicate( + private val identifiers: Set<String> +) : Predicate<Event> { + + constructor(vararg identifiers: String) : this(identifiers.toSet()) + + override fun applicable(thing: Event): Boolean { + val selection = (thing as? SelectionEvent) ?: return false + val isAction = selection.selectionKind == "action" + val idMatches = selection.selectedIdentifier in identifiers + return isAction && idMatches + } +} diff --git a/lib-kmm-foundation/src/commonTest/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicateTest.kt b/lib-kmm-foundation/src/commonTest/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicateTest.kt new file mode 100644 index 00000000..8428473e --- /dev/null +++ b/lib-kmm-foundation/src/commonTest/kotlin/com/hadisatrio/libs/kotlin/foundation/event/ActionSelectionEventPredicateTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Hadi Satrio + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.hadisatrio.libs.kotlin.foundation.event + +import io.kotest.matchers.booleans.shouldBeFalse +import io.kotest.matchers.booleans.shouldBeTrue +import kotlin.test.Test + +class ActionSelectionEventPredicateTest { + + @Test + fun `Matches only action selection events with the given identifier`() { + val identifiers = setOf("one", "another") + val events = identifiers.map { SelectionEvent("action", it) } + + val predicate = ActionSelectionEventPredicate(identifiers) + + events.forEach { event -> predicate.applicable(event).shouldBeTrue() } + predicate.applicable(SelectionEvent("action", "unknown")).shouldBeFalse() + predicate.applicable(SelectionEvent("not_action", "one")).shouldBeFalse() + } +}