Skip to content

Commit

Permalink
Navigate from supervised feature to supervisor
Browse files Browse the repository at this point in the history
  • Loading branch information
MiSikora committed Mar 28, 2021
1 parent 3064635 commit b75e380
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 46 deletions.
3 changes: 3 additions & 0 deletions library/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Navigation from supervised feature to supervisor.

### Changed
- Upgrade to Kotlin `1.4.32`.
- Upgrade to LifecycleViewmodelKtx `2.3.1`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import io.mehow.laboratory.Feature
import io.mehow.laboratory.inspector.OptionViewGroup.OnSelectFeatureListener
import io.mehow.laboratory.inspector.SourceViewGroup.OnSelectSourceListener

Expand All @@ -28,5 +29,9 @@ internal class FeatureAdapter(
override fun getChangePayload(old: FeatureUiModel, new: FeatureUiModel) = Unit
}

interface Listener : OnSelectFeatureListener, OnSelectSourceListener
interface Listener : OnSelectFeatureListener, OnSelectSourceListener {
@JvmDefault override fun onSelectSource(feature: Feature<*>) = onSelectFeature(feature)

fun onGoToFeature(feature: Class<Feature<*>>)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.mehow.laboratory.inspector

internal data class FeatureCoordinates(
val sectionIndex: Int,
val featureIndex: Int,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.mehow.laboratory.inspector

import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.View
import androidx.core.text.buildSpannedString
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.google.android.material.textview.MaterialTextView
Expand All @@ -15,25 +18,36 @@ internal class FeatureViewHolder(
itemView: View,
listener: FeatureAdapter.Listener,
) : ViewHolder(itemView) {
private var uiModel: FeatureUiModel? = null
private val context = itemView.context
private val nameControl = itemView.findViewById<MaterialTextView>(R.id.io_mehow_laboratory_feature_name)
private val supervisorControl = itemView.findViewById<MaterialTextView>(R.id.io_mehow_laboratory_feature_supervisor)
private val descriptionControl = itemView.findViewById<MaterialTextView>(R.id.io_mehow_laboratory_feature_description)
private val sourcesControl = itemView.findViewById<SourceViewGroup>(R.id.io_mehow_laboratory_feature_sources)
private val dividerControl = itemView.findViewById<View>(R.id.io_mehow_laboratory_sources_divider)
private val optionsControl = itemView.findViewById<OptionViewGroup>(R.id.io_mehow_laboratory_feature_options)
private val goToSupervisor = object : ClickableSpan() {
override fun onClick(widget: View) {
listener.onGoToFeature(uiModel!!.supervisorOption!!.javaClass)
}
}

init {
sourcesControl.setOnSelectSourceListener(listener)
optionsControl.setOnSelectFeatureListener(listener)
descriptionControl.movementMethod = LinkMovementMethod.getInstance()
supervisorControl.movementMethod = LinkMovementMethod.getInstance()
}

fun bind(uiModel: FeatureUiModel) = with(uiModel) {
bindName()
bindSupervisor()
bindDescription()
bindSources()
bindOptions()
fun bind(uiModel: FeatureUiModel) {
this.uiModel = uiModel
with(uiModel) {
bindName()
bindSupervisor()
bindDescription()
bindSources()
bindOptions()
}
}

private fun FeatureUiModel.bindName() {
Expand All @@ -48,8 +62,13 @@ internal class FeatureViewHolder(
private fun FeatureUiModel.bindSupervisor() = with(type) {
supervisorControl.isVisible = supervisorOption != null
supervisorControl.text = supervisorOption?.let { supervisorOption ->
val supervisorName = supervisorOption::class.simpleName
itemView.context.getString(R.string.io_mehow_laboratory_feature_supervisor, supervisorName, supervisorOption)
buildSpannedString {
append(context.getString(R.string.io_mehow_laboratory_feature_supervisor_prefix))
val linkStart = length
append(supervisorOption::class.simpleName)
setSpan(goToSupervisor, linkStart, length, SPAN_EXCLUSIVE_EXCLUSIVE)
append(context.getString(R.string.io_mehow_laboratory_feature_supervisor_suffix, supervisorOption))
}
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.milliseconds
Expand All @@ -42,7 +48,7 @@ internal class InspectorViewModel(
emitAll(searchQueries)
}.distinctUntilChanged()

private val groupFlows = featureFactories.mapValues { (_, featureFactory) ->
private val sectionFlows = featureFactories.mapValues { (_, featureFactory) ->
flow {
val groups = withContext(Dispatchers.Default) {
featureFactory.create()
Expand All @@ -57,12 +63,24 @@ internal class InspectorViewModel(
}.shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)
}

fun sectionFlow(sectionName: String) = groupFlows[sectionName] ?: emptyFlow()
fun sectionFlow(sectionName: String) = sectionFlows[sectionName] ?: emptyFlow()

fun selectFeature(feature: Feature<*>) {
viewModelScope.launch(start = UNDISPATCHED) { laboratory.setOption(feature) }
}

private val mutableNavigationFlow = MutableSharedFlow<FeatureCoordinates>()

val featureCoordinatesFlow: Flow<FeatureCoordinates> get() = mutableNavigationFlow

suspend fun goTo(feature: Class<Feature<*>>) = sectionFlows.values.asFlow().withIndex()
.mapNotNull { (sectionIndex, sectionFlow) ->
val listIndex = sectionFlow.first().map(FeatureUiModel::type).indexOf(feature)
if (listIndex == -1) null else FeatureCoordinates(sectionIndex, listIndex)
}
.firstOrNull()
?.also { mutableNavigationFlow.emit(it) }

private class FeatureMetadata(
private val feature: Class<Feature<*>>,
private val deprecationHandler: DeprecationHandler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.willowtreeapps.hyperion.plugin.v1.HyperionIgnore
import io.mehow.laboratory.FeatureFactory
import io.mehow.laboratory.Laboratory
import io.mehow.laboratory.inspector.LaboratoryActivity.Configuration.OffscreenSectionsBehavior.Limited
import io.mehow.laboratory.inspector.LaboratoryActivity.Configuration.OffscreenSectionsBehavior.Unlimited
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
Expand All @@ -26,7 +28,11 @@ import kotlinx.coroutines.launch
*/
@HyperionIgnore // https://github.com/willowtreeapps/Hyperion-Android/issues/194
public class LaboratoryActivity : AppCompatActivity(R.layout.io_mehow_laboratory_inspector) {
private val sectionNames = configuration.sectionNames.toList()
private val searchViewModel by viewModels<SearchViewModel> { SearchViewModel.Factory }
private val inspectorViewModel by viewModels<InspectorViewModel> {
InspectorViewModel.Factory(configuration, searchViewModel)
}

override fun onCreate(inState: Bundle?) {
super.onCreate(inState)
Expand All @@ -46,11 +52,12 @@ public class LaboratoryActivity : AppCompatActivity(R.layout.io_mehow_laboratory
}

private fun setUpViewPager() {
val sectionNames = configuration.sectionNames.toList()
val viewPager = findViewById<ViewPager2>(R.id.io_mehow_laboratory_view_pager).apply {
adapter = GroupAdapter(this@LaboratoryActivity, sectionNames)
adapter = SectionAdapter(this@LaboratoryActivity, sectionNames)
disableScrollEffect()
}
observeNavigationEvents(viewPager)

if (sectionNames.size <= 1) return
val tabLayout = findViewById<TabLayout>(R.id.io_mehow_laboratory_tab_layout).apply {
isVisible = true
Expand All @@ -60,6 +67,13 @@ public class LaboratoryActivity : AppCompatActivity(R.layout.io_mehow_laboratory
}.attach()
}

private fun observeNavigationEvents(viewPager: ViewPager2) = inspectorViewModel.featureCoordinatesFlow
.onEach { (sectionIndex, featureIndex) ->
viewPager.currentItem = sectionIndex
(viewPager.adapter as SectionAdapter).awaitSectionFragment(sectionNames[sectionIndex]).scrollTo(featureIndex)
}
.launchIn(lifecycleScope)

private fun resetFeatureFlags() = lifecycleScope.launch {
val isCleared = configuration.laboratory.clear()
val messageId = if (isCleared) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.mehow.laboratory.inspector

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import kotlinx.coroutines.delay
import java.lang.ref.WeakReference

internal class SectionAdapter(
activity: FragmentActivity,
private val sectionNames: List<String>,
) : FragmentStateAdapter(activity) {
private val pages = mutableMapOf<String, WeakReference<SectionFragment>>()

override fun getItemCount() = sectionNames.size

override fun createFragment(position: Int): Fragment {
val sectionName = sectionNames[position]
return SectionFragment.create(sectionName).also {
pages[sectionName] = WeakReference(it)
}
}

suspend fun awaitSectionFragment(sectionName: String): SectionFragment = pages[sectionName]?.get() ?: run {
delay(100) // ¯\_(ツ)_/¯
awaitSectionFragment(sectionName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,51 @@ import android.view.View
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.mehow.laboratory.Feature
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

internal class GroupFragment : Fragment(R.layout.io_mehow_laboratory_feature_group) {
private val sectionName get() = requireStringArgument(sectionKey)
private val searchViewModel by activityViewModels<SearchViewModel> { SearchViewModel.Factory }
val viewModel by viewModels<InspectorViewModel> {
internal class SectionFragment : Fragment(R.layout.io_mehow_laboratory_feature_group) {
val inspectorViewModel by activityViewModels<InspectorViewModel> {
InspectorViewModel.Factory(LaboratoryActivity.configuration, searchViewModel)
}
private val searchViewModel by activityViewModels<SearchViewModel> { SearchViewModel.Factory }
private val sectionName get() = requireStringArgument(sectionKey)

private lateinit var layoutManager: SmoothScrollingLinearLayoutManager
private val featureAdapter = FeatureAdapter(object : FeatureAdapter.Listener {
override fun onSelectFeature(feature: Feature<*>) {
viewModel.selectFeature(feature)
}
override fun onSelectFeature(feature: Feature<*>) = inspectorViewModel.selectFeature(feature)

override fun onSelectSource(feature: Feature<*>) = viewModel.selectFeature(feature)
override fun onGoToFeature(feature: Class<Feature<*>>) {
lifecycleScope.launch { inspectorViewModel.goTo(feature) }
}
})

override fun onViewCreated(view: View, inState: Bundle?) {
view.findViewById<RecyclerView>(R.id.io_mehow_laboratory_feature_group).apply {
layoutManager = LinearLayoutManager(requireActivity())
view.findViewById<RecyclerView>(R.id.io_mehow_laboratory_feature_section).apply {
layoutManager = SmoothScrollingLinearLayoutManager(requireActivity()).also {
this@SectionFragment.layoutManager = it
}
adapter = featureAdapter
hideKeyboardOnScroll()
}
observeGroup()
}

private fun observeGroup() = viewModel.sectionFlow(sectionName)
private fun observeGroup() = inspectorViewModel.sectionFlow(sectionName)
.onEach { featureAdapter.submitList(it) }
.launchIn(viewLifecycleOwner.lifecycleScope)

fun scrollTo(index: Int) = layoutManager.smoothScrollTo(index)

companion object {
private const val sectionKey = "Section.Key"

fun create(section: String): GroupFragment {
return GroupFragment().apply {
fun create(section: String): SectionFragment {
return SectionFragment().apply {
arguments = bundleOf(sectionKey to section)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.mehow.laboratory.inspector

import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller

internal class SmoothScrollingLinearLayoutManager(
context: Context,
) : LinearLayoutManager(context) {
private val scroller = object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference() = SNAP_TO_START

override fun getHorizontalSnapPreference() = SNAP_TO_START
}

fun smoothScrollTo(index: Int) {
scroller.targetPosition = index
startSmoothScroll(scroller)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/io_mehow_laboratory_feature_group"
android:id="@+id/io_mehow_laboratory_feature_section"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
Expand Down
3 changes: 2 additions & 1 deletion library/inspector/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
<string name="io_mehow_laboratory_search_features_hint">Search features…</string>
<string name="io_mehow_laboratory_clear_query">Clear query</string>
<string name="io_mehow_laboratory_close_search">Close search</string>
<string name="io_mehow_laboratory_feature_supervisor">Turned on if %1$s is %2$s</string>
<string name="io_mehow_laboratory_feature_supervisor_prefix">"Turned on if "</string>
<string name="io_mehow_laboratory_feature_supervisor_suffix">" is %1$s"</string>
</resources>
Loading

0 comments on commit b75e380

Please sign in to comment.