Skip to content

Commit

Permalink
NTV-223: overview & faq fragments & tabNavigation (#1410)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arkariang committed Sep 28, 2021
1 parent ea897c1 commit 846ad7d
Show file tree
Hide file tree
Showing 16 changed files with 398 additions and 7 deletions.
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Expand Up @@ -178,8 +178,9 @@
android:theme="@style/SettingsActivity" />
<activity
android:name=".ui.activities.ProjectPageActivity"
android:parentActivityName=".ui.activities.DiscoveryActivity"
android:theme="@style/ProjectActivity"
android:parentActivityName=".ui.activities.PlaygroundActivity" />
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.activities.ProjectNotificationSettingsActivity"
android:parentActivityName=".ui.activities.SettingsActivity"
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/kickstarter/libs/Configure.kt
@@ -0,0 +1,7 @@
package com.kickstarter.libs

import com.kickstarter.ui.data.ProjectData

interface Configure {
fun configureWith(projectData: ProjectData)
}
Expand Up @@ -60,6 +60,7 @@ private ProjectFactory() {}
.staticUsdRate(1.0f)
.usdExchangeRate(1.0f)
.slug(slug)
.projectFaqs(ProjectFaqFactory.Companion.getFaqs())
.updatedAt(DateTime.now())
.urls(Project.Urls.builder().web(web).build())
.video(VideoFactory.video())
Expand Down
@@ -0,0 +1,19 @@
package com.kickstarter.mock.factories

import com.kickstarter.models.ProjectFaq
import org.joda.time.DateTime

class ProjectFaqFactory private constructor() {
companion object {
fun getFaqs(): List<ProjectFaq> {
return listOf(getFaq(), getFaq(), getFaq())
}

private fun getFaq() = ProjectFaq.builder()
.id(7L)
.answer("No, there is no extra VAT or taxes for backers.\\r\\n\\r\\nWe will export the tables to local countries first and forward to respective shipping addresses through local couriers. We will clear the customs for all the desks. \\r\\n\\r\\nVAT and taxes are already included in the reward price and no extra payment will be required from backers.")
.question("Is there any extra VAT and taxes on top of shipping cost?")
.createdAt(DateTime.now())
.build()
}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/kickstarter/ui/ArgumentsKey.java
Expand Up @@ -8,4 +8,5 @@ private ArgumentsKey() {}
public static final String NEW_CARD_PROJECT = "com.kickstarter.ui.fragments.NewCardFragment.project";
public static final String PLEDGE_PLEDGE_DATA= "com.kickstarter.ui.fragments.PledgeFragment.pledge_data";
public static final String PLEDGE_PLEDGE_REASON = "com.kickstarter.ui.fragments.PledgeFragment.pledge_reason";
public static final String PROJECT_PAGER_POSITION = "com.kickstarter.ui.fragments.position";
}
Expand Up @@ -18,6 +18,8 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentManager
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.kickstarter.R
import com.kickstarter.databinding.ActivityProjectPageBinding
Expand All @@ -36,6 +38,7 @@ import com.kickstarter.libs.utils.extensions.toVisibility
import com.kickstarter.models.Project
import com.kickstarter.models.StoredCard
import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.adapters.ProjectPagerAdapter
import com.kickstarter.ui.data.CheckoutData
import com.kickstarter.ui.data.LoginReason
import com.kickstarter.ui.data.PledgeData
Expand All @@ -52,6 +55,11 @@ import com.kickstarter.viewmodels.ProjectPageViewModel
import com.stripe.android.view.CardInputWidget
import rx.android.schedulers.AndroidSchedulers

val fragmentsArray = arrayOf(
"Overview",
"Faq",
)

@RequiresActivityViewModel(ProjectPageViewModel.ViewModel::class)
class ProjectPageActivity :
BaseActivity<ProjectPageViewModel.ViewModel>(),
Expand All @@ -67,14 +75,16 @@ class ProjectPageActivity :

private val animDuration = 200L
private lateinit var binding: ActivityProjectPageBinding
private var pagerAdapter = ProjectPagerAdapter(supportFragmentManager, lifecycle)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProjectPageBinding.inflate(layoutInflater)

setContentView(binding.root)
this.ksString = environment().ksString()

configurePager()

val viewTreeObserver = binding.pledgeContainerLayout.pledgeContainerRoot.viewTreeObserver
if (viewTreeObserver.isAlive) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
Expand All @@ -98,6 +108,13 @@ class ProjectPageActivity :
}
}

this.viewModel.outputs.projectData()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
pagerAdapter.updatedWithProjectData(it)
}

this.viewModel.outputs.backingDetailsSubtitle()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
Expand Down Expand Up @@ -294,6 +311,34 @@ class ProjectPageActivity :
setClickListeners()
}

private fun configurePager() {
val viewPager = binding.projectPager
val tabLayout = binding.projectDetailTabs

pagerAdapter = ProjectPagerAdapter(supportFragmentManager, lifecycle)

viewPager.adapter = pagerAdapter

TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = fragmentsArray[position]
}.attach()

tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {

override fun onTabSelected(tab: TabLayout.Tab?) {
viewModel.inputs.tabSelected()
}

override fun onTabReselected(tab: TabLayout.Tab?) {
// Handle tab reselect
}

override fun onTabUnselected(tab: TabLayout.Tab?) {
// Handle tab unselect
}
})
}

override fun onResume() {
super.onResume()

Expand Down
@@ -0,0 +1,36 @@
package com.kickstarter.ui.adapters

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.kickstarter.libs.Configure
import com.kickstarter.ui.data.ProjectData
import com.kickstarter.ui.fragments.ProjectFaqFragment
import com.kickstarter.ui.fragments.ProjectOverviewFragment

class ProjectPagerAdapter(
private val fragmentManager: FragmentManager,
lifecycle: Lifecycle
) :
FragmentStateAdapter(fragmentManager, lifecycle) {

override fun getItemCount(): Int = 2

// TODO: improve when with an enum type
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> return ProjectOverviewFragment.newInstance(position)
1 -> return ProjectFaqFragment.newInstance(position)
else -> ProjectOverviewFragment.newInstance(position)
}
}

fun updatedWithProjectData(projectData: ProjectData) {
fragmentManager.fragments.forEach { fragment ->
// - fragmentManager.fragments will iterate over all fragments added, but only the ones on the
// - projectTabLayout implement the Configure interface
if (fragment is Configure) fragment.configureWith(projectData)
}
}
}
@@ -0,0 +1,54 @@
package com.kickstarter.ui.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.kickstarter.databinding.FragmentProjectFaqBinding
import com.kickstarter.libs.BaseFragment
import com.kickstarter.libs.Configure
import com.kickstarter.libs.qualifiers.RequiresFragmentViewModel
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.ui.ArgumentsKey
import com.kickstarter.ui.data.ProjectData
import com.kickstarter.viewmodels.ProjectFaqViewModel

@RequiresFragmentViewModel(ProjectFaqViewModel.ViewModel::class)
class ProjectFaqFragment : BaseFragment<ProjectFaqViewModel.ViewModel>(), Configure {
private var binding: FragmentProjectFaqBinding? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
binding = FragmentProjectFaqBinding.inflate(inflater, container, false)
return binding?.root
}

override fun configureWith(projectData: ProjectData) {
this.viewModel?.inputs?.configureWith(projectData)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// TODO: FAQ's ready for complete https://kickstarter.atlassian.net/browse/NTV-209
this.viewModel.outputs.projectFaqs()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe {
var faq = ""
it.map { faq += "***QUESTION: ${it.question} \n ***ANSWER:${it.answer} \n" }
binding?.placeholder?.text = faq
}
}

companion object {
@JvmStatic
fun newInstance(position: Int): ProjectFaqFragment {
val fragment = ProjectFaqFragment()
val bundle = Bundle()
bundle.putInt(ArgumentsKey.PROJECT_PAGER_POSITION, position)
fragment.arguments = bundle
return fragment
}
}
}
@@ -0,0 +1,44 @@
package com.kickstarter.ui.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.kickstarter.databinding.FragmentProjectOverviewBinding
import com.kickstarter.libs.BaseFragment
import com.kickstarter.libs.Configure
import com.kickstarter.libs.qualifiers.RequiresFragmentViewModel
import com.kickstarter.ui.ArgumentsKey
import com.kickstarter.ui.data.ProjectData
import com.kickstarter.viewmodels.ProjectOverviewViewModel

@RequiresFragmentViewModel(ProjectOverviewViewModel.ViewModel::class)
class ProjectOverviewFragment : BaseFragment<ProjectOverviewViewModel.ViewModel>(), Configure {

private var binding: FragmentProjectOverviewBinding? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
binding = FragmentProjectOverviewBinding.inflate(inflater, container, false)
return binding?.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}

override fun configureWith(projectData: ProjectData) {
this.viewModel?.inputs?.configureWith(projectData)
}

companion object {
@JvmStatic
fun newInstance(position: Int): ProjectOverviewFragment {
val fragment = ProjectOverviewFragment()
val bundle = Bundle()
bundle.putInt(ArgumentsKey.PROJECT_PAGER_POSITION, position)
fragment.arguments = bundle
return fragment
}
}
}
@@ -0,0 +1,51 @@
package com.kickstarter.viewmodels

import androidx.annotation.NonNull
import com.kickstarter.libs.Environment
import com.kickstarter.libs.FragmentViewModel
import com.kickstarter.libs.utils.ObjectUtils
import com.kickstarter.models.ProjectFaq
import com.kickstarter.ui.data.ProjectData
import com.kickstarter.ui.fragments.ProjectFaqFragment
import rx.Observable
import rx.subjects.BehaviorSubject
import rx.subjects.PublishSubject

class ProjectFaqViewModel {
interface Inputs {
/** Configure with current [ProjectData]. */
fun configureWith(projectData: ProjectData)
}

interface Outputs {
fun projectFaqs(): Observable<List<ProjectFaq>>
}

class ViewModel(@NonNull val environment: Environment) : FragmentViewModel<ProjectFaqFragment>(environment), Inputs, Outputs {
val inputs: Inputs = this
val outputs: Outputs = this

// - Inputs
private val projectDataInput = BehaviorSubject.create<ProjectData>()

// - Outputs
private val listFaqs = PublishSubject.create<List<ProjectFaq>>()

init {
// TODO: FAQ's ready for complete https://kickstarter.atlassian.net/browse/NTV-209
projectDataInput
.map { it?.project()?.projectFaqs() }
.filter { ObjectUtils.isNotNull(it) }
.map { requireNotNull(it) }
.compose(bindToLifecycle())
.subscribe {
this.listFaqs.onNext(it.toList())
}
}

// - Inputs
override fun configureWith(projectData: ProjectData) = this.projectDataInput.onNext(projectData)

override fun projectFaqs(): Observable<List<ProjectFaq>> = this.listFaqs
}
}
@@ -0,0 +1,34 @@
package com.kickstarter.viewmodels

import androidx.annotation.NonNull
import com.kickstarter.libs.Environment
import com.kickstarter.libs.FragmentViewModel
import com.kickstarter.libs.utils.ObjectUtils
import com.kickstarter.ui.data.ProjectData
import com.kickstarter.ui.fragments.ProjectOverviewFragment
import rx.subjects.BehaviorSubject

class ProjectOverviewViewModel {
interface Inputs {
/** Configure with current [ProjectData]. */
fun configureWith(projectData: ProjectData)
}

interface Outputs

class ViewModel(@NonNull val environment: Environment) : FragmentViewModel<ProjectOverviewFragment>(environment), Inputs, Outputs {
val inputs: Inputs = this
val outputs: Outputs = this

private val projectDataInput = BehaviorSubject.create<ProjectData>()

init {
val project = projectDataInput
.map { it.project() }
.filter { ObjectUtils.isNotNull(it) }
.map { requireNotNull(it) }
}

override fun configureWith(projectData: ProjectData) = this.projectDataInput.onNext(projectData)
}
}

0 comments on commit 846ad7d

Please sign in to comment.