diff --git a/ui/cyoa-renderer/src/main/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivity.kt b/ui/cyoa-renderer/src/main/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivity.kt index 2fabbf5ae6..c269cedca9 100644 --- a/ui/cyoa-renderer/src/main/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivity.kt +++ b/ui/cyoa-renderer/src/main/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivity.kt @@ -6,19 +6,26 @@ import android.os.Bundle import android.view.MenuItem import androidx.activity.viewModels import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE import androidx.fragment.app.commit +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asFlow import androidx.lifecycle.asLiveData +import androidx.lifecycle.switchMap import dagger.hilt.android.AndroidEntryPoint import java.util.Locale import kotlinx.coroutines.flow.combine import org.ccci.gto.android.common.androidx.fragment.app.backStackEntries import org.ccci.gto.android.common.androidx.fragment.app.hasPendingActions +import org.ccci.gto.android.common.androidx.lifecycle.ImmutableLiveData +import org.ccci.gto.android.common.androidx.lifecycle.combineWith import org.ccci.gto.android.common.androidx.lifecycle.toggleValue import org.cru.godtools.base.HOST_DYNALINKS import org.cru.godtools.base.HOST_GODTOOLSAPP_COM import org.cru.godtools.base.SCHEME_GODTOOLS +import org.cru.godtools.base.URI_SHARE_BASE import org.cru.godtools.base.tool.activity.MultiLanguageToolActivity import org.cru.godtools.base.tool.model.Event import org.cru.godtools.shared.tool.parser.model.Manifest @@ -49,6 +56,14 @@ class CyoaActivity : // track this tool open if (savedInstanceState == null) dataModel.toolCode.value?.let { trackToolOpen(it, Manifest.Type.CYOA) } + supportFragmentManager.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentStarted(fm: FragmentManager, f: Fragment) = updatePageFragmentLiveData() + override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) = updatePageFragmentLiveData() + }, + false + ) + dataModel.activeManifest.observe(this) { it?.let { showInitialPageIfNecessary(it) } } } @@ -181,6 +196,31 @@ class CyoaActivity : // endregion Training Tips // endregion UI + // region Share Link Logic + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + public override val shareLinkUriLiveData by lazy { + dataModel.activeManifest.combineWith(activePageLiveData) { manifest, page -> + manifest?.buildShareLink(page)?.build()?.toString() + } + } + + private val activePageLiveData by lazy { + pageFragmentLiveData.switchMap { it?.page ?: ImmutableLiveData(null) } + } + + private fun Manifest.buildShareLink(page: Page?): Uri.Builder? { + val tool = code ?: return null + val locale = locale ?: return null + return URI_SHARE_BASE.buildUpon() + .appendEncodedPath(locale.toString().lowercase(Locale.ENGLISH)) + .appendPath("tool") + .appendPath("v2") + .appendPath(tool) + .apply { page?.let { appendPath(it.id) } } + .appendQueryParameter("icid", "gtshare") + } + // endregion Share Link Logic + // region Page management @VisibleForTesting internal val pageFragment @@ -190,6 +230,13 @@ class CyoaActivity : } private val activePage get() = pageFragment?.page?.value + private val pageFragmentLiveData = MutableLiveData?>(null) + + private fun updatePageFragmentLiveData() { + val fragment = supportFragmentManager.primaryNavigationFragment as? CyoaPageFragment<*, *> + if (pageFragmentLiveData.value !== fragment) pageFragmentLiveData.value = fragment + } + private fun showInitialPageIfNecessary(manifest: Manifest) { if (pageFragment != null) return diff --git a/ui/cyoa-renderer/src/test/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivityTest.kt b/ui/cyoa-renderer/src/test/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivityTest.kt index db5e9eba74..e9afa92a35 100644 --- a/ui/cyoa-renderer/src/test/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivityTest.kt +++ b/ui/cyoa-renderer/src/test/kotlin/org/cru/godtools/tool/cyoa/ui/CyoaActivityTest.kt @@ -23,6 +23,7 @@ import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue import kotlinx.coroutines.flow.MutableStateFlow @@ -44,6 +45,7 @@ import org.cru.godtools.shared.tool.parser.model.page.backgroundColor import org.cru.godtools.shared.tool.parser.model.page.backgroundImageGravity import org.cru.godtools.shared.tool.parser.model.page.backgroundImageScaleType import org.cru.godtools.tool.cyoa.BuildConfig.HOST_GODTOOLS_CUSTOM_URI +import org.cru.godtools.tool.cyoa.CyoaDeepLink import org.cru.godtools.tool.cyoa.R import org.junit.Rule import org.junit.runner.RunWith @@ -737,6 +739,71 @@ class CyoaActivityTest { } // endregion Update Manifest + // region Share Link Logic + @Test + fun `Share Link - contains current page`() { + manifestEnglish.value = manifest(listOf(page1, page2)) + + scenario { + it.onActivity { + it.shareLinkUriLiveData.observeForever { } + + assertEquals("https://knowgod.com/en/tool/v2/test/page1?icid=gtshare", it.shareLinkUriLiveData.value) + } + } + } + + @Test + fun `Share Link - updates on navigation`() { + manifestEnglish.value = manifest(listOf(page1, page2)) + + scenario { + it.onActivity { + it.shareLinkUriLiveData.observeForever { } + + it.showPage(page2) + it.assertPageStack("page1", "page2") + assertEquals("https://knowgod.com/en/tool/v2/test/page2?icid=gtshare", it.shareLinkUriLiveData.value) + + it.onBackPressed() + it.assertPageStack("page1") + assertEquals("https://knowgod.com/en/tool/v2/test/page1?icid=gtshare", it.shareLinkUriLiveData.value) + } + } + } + + @Test + fun `Share Link - parseable by CyoaDeepLink`() { + manifestEnglish.value = manifest(listOf(page1, page2)) + + scenario { + it.onActivity { + it.shareLinkUriLiveData.observeForever { } + + it.showPage(page2) + it.assertPageStack("page1", "page2") + + val uri = Uri.parse(it.shareLinkUriLiveData.value) + val deepLink = assertNotNull(CyoaDeepLink.parseKnowGodDeepLink(uri)) + assertEquals(TOOL, deepLink.tool) + assertEquals("page2", deepLink.page) + assertEquals(Locale.ENGLISH, deepLink.activeLocale) + } + } + } + + @Test + fun `Share Link - no active manifest`() { + scenario { + it.onActivity { + it.shareLinkUriLiveData.observeForever { } + + assertNull(it.shareLinkUriLiveData.value) + } + } + } + // endregion Share Link Logic + private val CyoaActivity.dataModel get() = viewModels().value private fun CyoaActivity.assertPageStack(vararg pages: String) {