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()
+    }
+}