diff --git a/android/DartsScorecard/app/build.gradle b/android/DartsScorecard/app/build.gradle index 6c2b7b96..00300d56 100644 --- a/android/DartsScorecard/app/build.gradle +++ b/android/DartsScorecard/app/build.gradle @@ -46,6 +46,7 @@ dependencies { implementation "com.google.firebase:firebase-firestore:$firebase" implementation "android.arch.lifecycle:extensions:$architecture" implementation "android.arch.persistence.room:runtime:$room" + implementation "com.github.yalantis:jellytoolbar:$jelly" implementation "com.github.bumptech.glide:glide:$glide" implementation "de.hdodenhof:circleimageview:$circleImageView" implementation("com.crashlytics.sdk.android:crashlytics:$crash") { diff --git a/android/DartsScorecard/app/src/main/AndroidManifest.xml b/android/DartsScorecard/app/src/main/AndroidManifest.xml index f6d031e4..cb4fe281 100644 --- a/android/DartsScorecard/app/src/main/AndroidManifest.xml +++ b/android/DartsScorecard/app/src/main/AndroidManifest.xml @@ -47,6 +47,10 @@ + diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/ViewModelActivity.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/ViewModelActivity.kt index 03a9c8bf..44413956 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/ViewModelActivity.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/ViewModelActivity.kt @@ -9,10 +9,12 @@ import android.preference.PreferenceManager import android.support.annotation.StringRes import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar +import android.view.MenuItem import nl.entreco.dartsscorecard.App import nl.entreco.dartsscorecard.R import nl.entreco.dartsscorecard.di.viewmodel.ViewModelComponent import nl.entreco.dartsscorecard.di.viewmodel.ViewModelModule +import nl.entreco.dartsscorecard.wtf.WtfActivity /** * Created by Entreco on 14/11/2017. @@ -56,6 +58,16 @@ abstract class ViewModelActivity : AppCompatActivity() { styler.switch() } + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + return when (item?.itemId) { + R.id.menu_faq -> { + WtfActivity.launch(this) + true + } + else -> super.onOptionsItemSelected(item) + } + } + protected fun initToolbar(toolbar: Toolbar, @StringRes title: Int = R.string.app_name, showHomeEnabled: Boolean = true) { setSupportActionBar(toolbar) setTitle(title) diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaActivity.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaActivity.kt index 71b36793..945e6ff8 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaActivity.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaActivity.kt @@ -11,6 +11,7 @@ import android.support.design.widget.Snackbar import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.Toolbar +import android.view.Menu import android.view.MenuItem import nl.entreco.dartsscorecard.R import nl.entreco.dartsscorecard.base.ViewModelActivity @@ -64,6 +65,11 @@ class BetaActivity : ViewModelActivity(), DonateCallback, BetaAnimator.Swapper { viewModel.unsubscribe(this) } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.beta, menu) + return super.onCreateOptionsMenu(menu) + } + override fun lifeCycle(): Lifecycle { return lifecycle } diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaViewModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaViewModel.kt index 6415ce7a..9d550a1d 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaViewModel.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaViewModel.kt @@ -15,7 +15,6 @@ class BetaViewModel @Inject constructor(private val subscribeToFeaturesUsecase: private val features: MutableLiveData> = MutableLiveData() - fun refresh() { subscribeToFeaturesUsecase.subscribe({ features.value = it diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/donate/DonateViewModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/donate/DonateViewModel.kt index 6208004b..f075aa86 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/donate/DonateViewModel.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/donate/DonateViewModel.kt @@ -18,7 +18,7 @@ import javax.inject.Inject * Created by entreco on 08/02/2018. */ class DonateViewModel @Inject constructor( - private val donateCallback: DonateCallback, + private var donateCallback: DonateCallback?, private val connectToBillingUsecase: ConnectToBillingUsecase, private val fetchDonationsUsecase: FetchDonationsUsecase, private val makeDonation: MakeDonationUsecase, @@ -26,7 +26,7 @@ class DonateViewModel @Inject constructor( private val analytics: Analytics) : BaseViewModel(), LifecycleObserver { init { - donateCallback.lifeCycle().addObserver(this) + donateCallback?.lifeCycle()?.addObserver(this) } internal var productId: String = "" @@ -42,7 +42,7 @@ class DonateViewModel @Inject constructor( } private fun onStartMakeDonation(): (MakeDonationResponse) -> Unit = { response -> - donateCallback.makeDonation(response) + donateCallback?.makeDonation(response) } fun onMakeDonationSuccess(data: Intent?) { @@ -79,7 +79,7 @@ class DonateViewModel @Inject constructor( private fun donationDone(response: ConsumeDonationResponse) { donationWithId(response)?.let { donation -> analytics.trackPurchase(donation, response.orderId) - donateCallback.onDonationMade(donation) + donateCallback?.onDonationMade(donation) } } @@ -126,6 +126,11 @@ class DonateViewModel @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun destroy() { - donateCallback.lifeCycle().removeObserver(this) + donateCallback?.lifeCycle()?.removeObserver(this) + } + + override fun onCleared() { + donateCallback = null + super.onCleared() } } diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/ViewModelComponent.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/ViewModelComponent.kt index 5f2de695..433c0374 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/ViewModelComponent.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/ViewModelComponent.kt @@ -13,9 +13,12 @@ import nl.entreco.dartsscorecard.di.setup.EditPlayerModule import nl.entreco.dartsscorecard.di.setup.Setup01Component import nl.entreco.dartsscorecard.di.setup.Setup01Module import nl.entreco.dartsscorecard.di.viewmodel.ad.AdModule +import nl.entreco.dartsscorecard.di.viewmodel.api.FaqApiModule import nl.entreco.dartsscorecard.di.viewmodel.api.FeatureApiModule import nl.entreco.dartsscorecard.di.viewmodel.db.* import nl.entreco.dartsscorecard.di.viewmodel.threading.ThreadingModule +import nl.entreco.dartsscorecard.di.wtf.WtfComponent +import nl.entreco.dartsscorecard.di.wtf.WtfModule /** * Created by Entreco on 14/11/2017. @@ -23,13 +26,14 @@ import nl.entreco.dartsscorecard.di.viewmodel.threading.ThreadingModule @ActivityScope @Subcomponent(modules = [(ViewModelModule::class), (ThreadingModule::class), (AdModule::class), (GameDbModule::class), (PlayerDbModule::class), (TurnDbModule::class), - (MetaDbModule::class), (StatDbModule::class), (FeatureApiModule::class)]) + (MetaDbModule::class), (StatDbModule::class), (FeatureApiModule::class), (FaqApiModule::class)]) interface ViewModelComponent { // Where can this be used fun plus(module: LaunchModule): LaunchComponent fun plus(module: BetaModule): BetaComponent + fun plus(module: WtfModule): WtfComponent fun plus(module: Setup01Module): Setup01Component fun plus(module: EditPlayerModule): EditPlayerComponent fun plus(module: Play01Module): Play01Component diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/api/FaqApiModule.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/api/FaqApiModule.kt new file mode 100644 index 00000000..98147d80 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/viewmodel/api/FaqApiModule.kt @@ -0,0 +1,17 @@ +package nl.entreco.dartsscorecard.di.viewmodel.api + +import com.google.firebase.firestore.FirebaseFirestore +import dagger.Module +import dagger.Provides +import nl.entreco.data.wtf.RemoteWtfRepository +import nl.entreco.domain.common.log.Logger +import nl.entreco.domain.repository.WtfRepository + +@Module +class FaqApiModule { + + @Provides + fun provideRemoteFaqRepository(db: FirebaseFirestore, logger: Logger): WtfRepository { + return RemoteWtfRepository(db, logger) + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfComponent.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfComponent.kt new file mode 100644 index 00000000..b3a7698c --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfComponent.kt @@ -0,0 +1,12 @@ +package nl.entreco.dartsscorecard.di.wtf + +import dagger.Subcomponent +import nl.entreco.dartsscorecard.wtf.WtfAdapter +import nl.entreco.dartsscorecard.wtf.WtfViewModel + +@WtfScope +@Subcomponent(modules = [(WtfModule::class)]) +interface WtfComponent { + fun viewModel(): WtfViewModel + fun adapter(): WtfAdapter +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfModule.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfModule.kt new file mode 100644 index 00000000..60db3918 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfModule.kt @@ -0,0 +1,6 @@ +package nl.entreco.dartsscorecard.di.wtf + +import dagger.Module + +@Module +class WtfModule \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfScope.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfScope.kt new file mode 100644 index 00000000..60b9eb56 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/wtf/WtfScope.kt @@ -0,0 +1,7 @@ +package nl.entreco.dartsscorecard.di.wtf + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class WtfScope \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/launch/LaunchViewModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/launch/LaunchViewModel.kt index 683b94d3..9b6fb7df 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/launch/LaunchViewModel.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/launch/LaunchViewModel.kt @@ -7,6 +7,7 @@ import nl.entreco.dartsscorecard.beta.BetaActivity import nl.entreco.dartsscorecard.play.Play01Activity import nl.entreco.dartsscorecard.profile.select.SelectProfileActivity import nl.entreco.dartsscorecard.setup.Setup01Activity +import nl.entreco.dartsscorecard.wtf.WtfActivity import nl.entreco.domain.launch.FetchLatestGameResponse import nl.entreco.domain.launch.RetrieveLatestGameUsecase import nl.entreco.domain.setup.game.CreateGameResponse diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/profile/select/SelectProfileActivity.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/profile/select/SelectProfileActivity.kt index 4177fdcd..fbfbf138 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/profile/select/SelectProfileActivity.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/profile/select/SelectProfileActivity.kt @@ -9,6 +9,7 @@ import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.support.v7.widget.Toolbar +import android.view.Menu import nl.entreco.dartsscorecard.R import nl.entreco.dartsscorecard.base.ViewModelActivity import nl.entreco.dartsscorecard.databinding.ActivitySelectProfileBinding @@ -63,6 +64,11 @@ class SelectProfileActivity : ViewModelActivity() { swipeToDeleteHelper.attachToRecyclerView(recyclerView) } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.select, menu) + return super.onCreateOptionsMenu(menu) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_CODE_VIEW && resultCode == Activity.RESULT_OK) { viewModel.reload(adapter) diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfActivity.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfActivity.kt new file mode 100644 index 00000000..45b473e3 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfActivity.kt @@ -0,0 +1,107 @@ +package nl.entreco.dartsscorecard.wtf + +import android.content.Context +import android.content.Intent +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v7.widget.DefaultItemAnimator +import android.support.v7.widget.GridLayoutManager +import android.support.v7.widget.Toolbar +import android.view.LayoutInflater +import android.view.MenuItem +import com.yalantis.jellytoolbar.listener.JellyListener +import nl.entreco.dartsscorecard.R +import nl.entreco.dartsscorecard.base.ViewModelActivity +import nl.entreco.dartsscorecard.databinding.ActivityWtfBinding +import nl.entreco.dartsscorecard.databinding.SearchbarBinding +import nl.entreco.dartsscorecard.di.wtf.WtfComponent +import nl.entreco.dartsscorecard.di.wtf.WtfModule +import android.support.v4.content.ContextCompat +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager + + +class WtfActivity : ViewModelActivity() { + + private lateinit var binding: ActivityWtfBinding + private lateinit var searchBinding: SearchbarBinding + private val component: WtfComponent by componentProvider { it.plus(WtfModule()) } + private val viewModel: WtfViewModel by viewModelProvider { component.viewModel() } + private val adapter: WtfAdapter by lazy { component.adapter() } + + private val jellyListener by lazy { + object : JellyListener() { + override fun onCancelIconClicked() { + val searchField = searchBinding.searchField + if (searchField.text.isEmpty()) { + binding.includeToolbar.toolbar.collapse() + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(searchField.windowToken, 0) + } else { + searchField.text.clear() + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_wtf) + binding.viewModel = viewModel + + initJellyBar() + initToolbar(toolbar(binding), R.string.title_wtf) + initRecyclerView(binding) + } + + private fun initJellyBar() { + binding.includeToolbar.toolbar.jellyListener = jellyListener + searchBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.searchbar, null, false) + searchBinding.listener = adapter + binding.includeToolbar.toolbar.contentView = searchBinding.root + } + + private fun initRecyclerView(binding: ActivityWtfBinding) { + val recyclerView = binding.wtfRecyclerView + recyclerView.setHasFixedSize(true) + recyclerView.setItemViewCacheSize(20) + recyclerView.layoutManager = GridLayoutManager(binding.root.context, 1) + recyclerView.itemAnimator = DefaultItemAnimator() + recyclerView.isDrawingCacheEnabled = true + recyclerView.adapter = adapter + } + + private fun toolbar(binding: ActivityWtfBinding): Toolbar { + return binding.includeToolbar.toolbar.toolbar!! + } + + override fun onResume() { + super.onResume() + viewModel.subscribe(this, adapter) + } + + override fun onPause() { + super.onPause() + viewModel.unsubscribe(this) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + return when (item?.itemId) { + android.R.id.home -> { + onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + companion object { + + @JvmStatic + fun launch(context: Context) { + val intent = Intent(context, WtfActivity::class.java) + context.startActivity(intent) + } + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfAdapter.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfAdapter.kt new file mode 100644 index 00000000..14d93c65 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfAdapter.kt @@ -0,0 +1,111 @@ +package nl.entreco.dartsscorecard.wtf + +import android.arch.lifecycle.Observer +import android.databinding.DataBindingUtil +import android.support.v7.util.DiffUtil +import android.view.LayoutInflater +import android.view.ViewGroup +import nl.entreco.dartsscorecard.R +import nl.entreco.dartsscorecard.base.TestableAdapter +import nl.entreco.dartsscorecard.databinding.WtfViewBinding +import nl.entreco.domain.common.executors.Background +import nl.entreco.domain.common.executors.Foreground +import nl.entreco.domain.wtf.SubmitViewedItemRequest +import nl.entreco.domain.wtf.SubmitViewedItemUsecase +import nl.entreco.domain.wtf.WtfItem +import java.util.* +import java.util.Locale.filter +import javax.inject.Inject + +class WtfAdapter @Inject constructor(private val bg: Background, private val fg: Foreground, + private val submitViewedItemUsecase: SubmitViewedItemUsecase) : TestableAdapter(), Observer>, WtfToggler, WtfSearchable { + + private var searchText : String = "" + private var allItems: MutableSet = mutableSetOf() + private val visibleItems: MutableList = mutableListOf() + private val queue: Queue> = ArrayDeque() + private var expandedItem: String? = null + private val lock: Any = Any() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WtfView { + val inflater = LayoutInflater.from(parent.context) + val binding = DataBindingUtil.inflate(inflater, R.layout.wtf_view, parent, false) + return WtfView(binding) + } + + override fun onBindViewHolder(holder: WtfView, position: Int) { + + synchronized(lock) { + val currentItem = visibleItems[position] + holder.bind(currentItem, currentItem.docId != expandedItem, this) + } + } + + override fun getItemCount(): Int { + return visibleItems.size + } + + override fun search(text: CharSequence) { + + if (text.isNotEmpty() && text.length >= 3) { + searchText = text.toString().toLowerCase() + onChanged(allItems.toList()) + } else { + clearSearch() + } + } + + private fun clearSearch() { + searchText = "" + onChanged(allItems.toList()) + } + + override fun toggle(item: WtfItem) { + synchronized(lock) { + expandedItem = if (item.docId == expandedItem) null + else item.docId + notifyItemRangeChanged(0, visibleItems.size) + submitViewedItemUsecase.exec(SubmitViewedItemRequest(item.docId)) + } + } + + override fun onChanged(features: List?) { + if (features != null) { + queue.add(features) + if (queue.size <= 1) { + calculateDiff(features) + } + } + } + + private fun calculateDiff(features: List) { + bg.post(Runnable { + allItems.addAll(features) + val filtSort = features.filter { doFilter(it, searchText) }.sortedByDescending { score(it, searchText) } + val diff = DiffUtil.calculateDiff(WtfDiffCalculator(visibleItems, filtSort), true) + fg.post(Runnable { + queue.remove() + updateItems(filtSort, diff) + if (queue.size > 0) { + calculateDiff(queue.peek()) + } + }) + }) + } + + private fun doFilter(item: WtfItem, searchText: String): Boolean { + if(searchText.isBlank()) return true + return item.title.toLowerCase().contains(searchText) || item.description.toLowerCase().contains(searchText) + } + private fun score(item: WtfItem, searchText: String): Int { + if(searchText.isBlank()) return 0 + return if(item.title.toLowerCase().contains(searchText)) 10 else 0 + + if(item.description.toLowerCase().contains(searchText)) 5 else 0 + } + + private fun updateItems(features: List, diff: DiffUtil.DiffResult) { + visibleItems.clear() + visibleItems.addAll(features) + diff.dispatchUpdatesTo(this) + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfDiffCalculator.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfDiffCalculator.kt new file mode 100644 index 00000000..4dfab971 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfDiffCalculator.kt @@ -0,0 +1,22 @@ +package nl.entreco.dartsscorecard.wtf + +import android.support.v7.util.DiffUtil +import nl.entreco.domain.wtf.WtfItem + +class WtfDiffCalculator(private val old: List, private val new: List) : DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition].docId == new[newItemPosition].docId + } + + override fun getOldListSize(): Int { + return old.size + } + + override fun getNewListSize(): Int { + return new.size + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition] == new[newItemPosition] + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfModel.kt new file mode 100644 index 00000000..1ee12716 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfModel.kt @@ -0,0 +1,38 @@ +package nl.entreco.dartsscorecard.wtf + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.databinding.ObservableBoolean +import android.databinding.ObservableField +import android.databinding.ObservableInt +import android.net.Uri +import android.view.View +import nl.entreco.domain.wtf.WtfItem + +class WtfModel(private val item: WtfItem, private val toggler: WtfToggler, collapse: Boolean = true) { + val title = ObservableField(item.title) + val description = ObservableField(item.description) + val image = ObservableField(item.image) + val showImage = ObservableInt(if(!collapse && item.image?.isNotBlank() == true) View.VISIBLE else View.GONE) + val showVideo = ObservableInt(if(!collapse && item.video?.isNotBlank() == true) View.VISIBLE else View.GONE) + val collapsed = ObservableBoolean(collapse) + + fun launchVideo(view: View){ + if(showVideo.get() == View.VISIBLE){ + val uri = Uri.parse(item.video) + val id = uri.getQueryParameter( "v" ) + val app = Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:$id")) + val web = Intent(Intent.ACTION_VIEW, uri) + + try { + view.context.startActivity(app) + } catch(youtubeNotInstalled: ActivityNotFoundException){ + view.context.startActivity(web) + } + } + } + + fun toggle(){ + toggler.toggle(item) + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfSearchable.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfSearchable.kt new file mode 100644 index 00000000..6d7682b1 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfSearchable.kt @@ -0,0 +1,5 @@ +package nl.entreco.dartsscorecard.wtf + +interface WtfSearchable { + fun search(text: CharSequence) +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfToggler.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfToggler.kt new file mode 100644 index 00000000..a3c1205e --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfToggler.kt @@ -0,0 +1,7 @@ +package nl.entreco.dartsscorecard.wtf + +import nl.entreco.domain.wtf.WtfItem + +interface WtfToggler { + fun toggle(item: WtfItem) +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfView.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfView.kt new file mode 100644 index 00000000..f95da599 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfView.kt @@ -0,0 +1,12 @@ +package nl.entreco.dartsscorecard.wtf + +import android.support.v7.widget.RecyclerView +import nl.entreco.dartsscorecard.databinding.WtfViewBinding +import nl.entreco.domain.wtf.WtfItem + +class WtfView(private val binding: WtfViewBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: WtfItem, collapsed: Boolean, toggler: WtfToggler) { + binding.wtf = WtfModel(item, toggler, collapsed) + binding.executePendingBindings() + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfViewModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfViewModel.kt new file mode 100644 index 00000000..53556ed1 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/wtf/WtfViewModel.kt @@ -0,0 +1,33 @@ +package nl.entreco.dartsscorecard.wtf + +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.Observer +import nl.entreco.dartsscorecard.base.BaseViewModel +import nl.entreco.domain.beta.Feature +import nl.entreco.domain.purchases.connect.SubscribeToFeaturesUsecase +import nl.entreco.domain.wtf.SubscribeToWtfsUsecase +import nl.entreco.domain.wtf.WtfItem +import javax.inject.Inject + +class WtfViewModel @Inject constructor(private val subscribeToWtfUsecase: SubscribeToWtfsUsecase) : BaseViewModel() { + + private val items: MutableLiveData> = MutableLiveData() + + fun refresh() { + subscribeToWtfUsecase.subscribe({ + items.value = it + }, {}) + } + + fun subscribe(owner: LifecycleOwner, observer: Observer>) { + items.observe(owner, observer) + refresh() + } + + fun unsubscribe(owner: LifecycleOwner) { + items.removeObservers(owner) + items.value = emptyList() + subscribeToWtfUsecase.unsubscribe() + } +} \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/anim/layout_animation_from_bottom.xml b/android/DartsScorecard/app/src/main/res/anim/layout_animation_from_bottom.xml index 441de770..7e4802a5 100644 --- a/android/DartsScorecard/app/src/main/res/anim/layout_animation_from_bottom.xml +++ b/android/DartsScorecard/app/src/main/res/anim/layout_animation_from_bottom.xml @@ -2,4 +2,4 @@ + android:delay="8%" /> diff --git a/android/DartsScorecard/app/src/main/res/drawable/faq_image_bg.xml b/android/DartsScorecard/app/src/main/res/drawable/faq_image_bg.xml new file mode 100644 index 00000000..1182829a --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/drawable/faq_image_bg.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/drawable/ic_arrow_drop_down.xml b/android/DartsScorecard/app/src/main/res/drawable/ic_arrow_drop_down.xml new file mode 100644 index 00000000..67f670ab --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/DartsScorecard/app/src/main/res/drawable/ic_clear.xml b/android/DartsScorecard/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 00000000..2a8b72a6 --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/DartsScorecard/app/src/main/res/drawable/ic_search.xml b/android/DartsScorecard/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..3661d395 --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/DartsScorecard/app/src/main/res/drawable/ic_wtf.xml b/android/DartsScorecard/app/src/main/res/drawable/ic_wtf.xml new file mode 100644 index 00000000..35ac03da --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/drawable/ic_wtf.xml @@ -0,0 +1,18 @@ + + + + diff --git a/android/DartsScorecard/app/src/main/res/layout/activity_wtf.xml b/android/DartsScorecard/app/src/main/res/layout/activity_wtf.xml new file mode 100644 index 00000000..6c979fdf --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/layout/activity_wtf.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/layout/jelly_toolbar.xml b/android/DartsScorecard/app/src/main/res/layout/jelly_toolbar.xml new file mode 100644 index 00000000..7b16b85c --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/layout/jelly_toolbar.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/layout/searchbar.xml b/android/DartsScorecard/app/src/main/res/layout/searchbar.xml new file mode 100644 index 00000000..8b5c32b0 --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/layout/searchbar.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/layout/toolbar_beta.xml b/android/DartsScorecard/app/src/main/res/layout/toolbar_beta.xml index fdb57f2e..570bd24d 100644 --- a/android/DartsScorecard/app/src/main/res/layout/toolbar_beta.xml +++ b/android/DartsScorecard/app/src/main/res/layout/toolbar_beta.xml @@ -14,7 +14,6 @@ android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" app:collapsedTitleGravity="start" app:contentScrim="?android:attr/colorPrimary" app:expandedTitleGravity="start" diff --git a/android/DartsScorecard/app/src/main/res/layout/toolbar_wtf.xml b/android/DartsScorecard/app/src/main/res/layout/toolbar_wtf.xml new file mode 100644 index 00000000..e65c88c3 --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/layout/toolbar_wtf.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/android/DartsScorecard/app/src/main/res/layout/wtf_view.xml b/android/DartsScorecard/app/src/main/res/layout/wtf_view.xml new file mode 100644 index 00000000..882248fd --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/layout/wtf_view.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/DartsScorecard/app/src/main/res/menu/beta.xml b/android/DartsScorecard/app/src/main/res/menu/beta.xml new file mode 100644 index 00000000..ddc9b093 --- /dev/null +++ b/android/DartsScorecard/app/src/main/res/menu/beta.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/menu/play.xml b/android/DartsScorecard/app/src/main/res/menu/play.xml index 5f25c8a7..a99e3dd8 100644 --- a/android/DartsScorecard/app/src/main/res/menu/play.xml +++ b/android/DartsScorecard/app/src/main/res/menu/play.xml @@ -1,5 +1,12 @@ - + + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/app/src/main/res/values/colors.xml b/android/DartsScorecard/app/src/main/res/values/colors.xml index f0c6696b..8bc5e5ed 100644 --- a/android/DartsScorecard/app/src/main/res/values/colors.xml +++ b/android/DartsScorecard/app/src/main/res/values/colors.xml @@ -6,6 +6,7 @@ #ffffff #979797 + #32979797 #000000 #313131 diff --git a/android/DartsScorecard/app/src/main/res/values/strings.xml b/android/DartsScorecard/app/src/main/res/values/strings.xml index a92699fd..129d0147 100644 --- a/android/DartsScorecard/app/src/main/res/values/strings.xml +++ b/android/DartsScorecard/app/src/main/res/values/strings.xml @@ -20,12 +20,14 @@ Edit Profile Configure 01 Game Beta Features + @string/wtf @string/resume Start new 01 Manage Profiles Beta + What the FAQ VOTE @@ -96,6 +98,9 @@ #20+ #Bust/ Bounce/ NoScore + + Search in F.A.Q. + Archive Stats Service Archive stats diff --git a/android/DartsScorecard/app/src/main/res/values/styles_beta.xml b/android/DartsScorecard/app/src/main/res/values/styles_beta.xml index 0d0f9ead..1583d8fb 100644 --- a/android/DartsScorecard/app/src/main/res/values/styles_beta.xml +++ b/android/DartsScorecard/app/src/main/res/values/styles_beta.xml @@ -19,7 +19,7 @@ italic @color/gray @dimen/xlarge - @color/colorAccent + ?attr/colorAccent + + + + + + + \ No newline at end of file diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/wtf/FaqApiData.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/wtf/FaqApiData.kt new file mode 100644 index 00000000..9519d302 --- /dev/null +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/wtf/FaqApiData.kt @@ -0,0 +1,24 @@ +package nl.entreco.data.wtf + +import android.support.annotation.Keep +import com.google.firebase.firestore.IgnoreExtraProperties +import com.google.firebase.firestore.PropertyName + +@Keep +@IgnoreExtraProperties +internal class FaqApiData { + @PropertyName("desc") + val desc: String = "" + + @PropertyName("image") + val image: String = "" + + @PropertyName("title") + val title: String = "" + + @PropertyName("video") + val video: String = "" + + @PropertyName("viewed") + val viewed: Long = 0 +} diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/wtf/RemoteWtfRepository.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/wtf/RemoteWtfRepository.kt new file mode 100644 index 00000000..4d3d3204 --- /dev/null +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/wtf/RemoteWtfRepository.kt @@ -0,0 +1,56 @@ +package nl.entreco.data.wtf + +import com.google.firebase.firestore.* +import nl.entreco.domain.common.log.Logger +import nl.entreco.domain.repository.WtfRepository +import nl.entreco.domain.wtf.WtfItem + +class RemoteWtfRepository(private val db: FirebaseFirestore, private val logger: Logger) : WtfRepository, EventListener { + + private val faqRef = db.collection("faq") + private val listener = faqRef.addSnapshotListener(this) + internal var onChange: (List) -> Unit = {} + private val wtfItems = mutableMapOf() + + override fun onEvent(p0: QuerySnapshot?, p1: FirebaseFirestoreException?) { + if (p1 != null) { + logger.w("Unable to read /faq's from Firestore") + return + } + + wtfItems.clear() + + p0?.documents?.forEach { doc -> + convertToFaqItem(doc) + }.also { onChange(ArrayList(wtfItems.values)) } + } + + private fun convertToFaqItem(doc: DocumentSnapshot) { + try { + val wtf = doc.toObject(FaqApiData::class.java)!! + wtfItems[doc.id] = WtfItem(doc.id, wtf.title, wtf.desc, wtf.image, wtf.video, wtf.viewed) + } catch (oops: Exception) { + logger.e("Unable to convert snapshot to Faq( $doc ) $oops") + } + } + + override fun viewedItem(docId: String) { + wtfItems[docId]?.also { + // Update on Remote + val faqItem = faqRef.document(docId) + db.runTransaction { transaction -> + transaction.update(faqItem, "viewed", it.viewed + 1) + } + } + } + + override fun subscribe(onChange: (List) -> Unit): List { + this.onChange = onChange + return ArrayList(wtfItems.values) + } + + override fun unsubscribe() { + this.onChange = {} + this.listener.remove() + } +} \ No newline at end of file diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/repository/WtfRepository.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/repository/WtfRepository.kt new file mode 100644 index 00000000..5d94e34a --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/repository/WtfRepository.kt @@ -0,0 +1,15 @@ +package nl.entreco.domain.repository + +import android.support.annotation.WorkerThread +import nl.entreco.domain.wtf.WtfItem + +interface WtfRepository { + @WorkerThread + fun subscribe(onChange: (List) -> Unit): List + + @WorkerThread + fun unsubscribe() + + @WorkerThread + fun viewedItem(docId: String) +} \ No newline at end of file diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubmitViewedItemRequest.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubmitViewedItemRequest.kt new file mode 100644 index 00000000..46030be7 --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubmitViewedItemRequest.kt @@ -0,0 +1,3 @@ +package nl.entreco.domain.wtf + +data class SubmitViewedItemRequest(val docId: String) \ No newline at end of file diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubmitViewedItemUsecase.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubmitViewedItemUsecase.kt new file mode 100644 index 00000000..f0c788f3 --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubmitViewedItemUsecase.kt @@ -0,0 +1,16 @@ +package nl.entreco.domain.wtf + +import nl.entreco.domain.BaseUsecase +import nl.entreco.domain.common.executors.Background +import nl.entreco.domain.common.executors.Foreground +import nl.entreco.domain.repository.WtfRepository +import javax.inject.Inject + +class SubmitViewedItemUsecase @Inject constructor(private val repo: WtfRepository, bg: Background, fg: Foreground) : BaseUsecase(bg, fg) { + + fun exec(request: SubmitViewedItemRequest) { + onBackground({ + repo.viewedItem(request.docId) + }, {}) + } +} \ No newline at end of file diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubscribeToWtfsUsecase.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubscribeToWtfsUsecase.kt new file mode 100644 index 00000000..046804a6 --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/SubscribeToWtfsUsecase.kt @@ -0,0 +1,28 @@ +package nl.entreco.domain.wtf + +import nl.entreco.domain.BaseUsecase +import nl.entreco.domain.common.executors.Background +import nl.entreco.domain.common.executors.Foreground +import nl.entreco.domain.repository.WtfRepository +import javax.inject.Inject + +class SubscribeToWtfsUsecase @Inject constructor(private val repo: WtfRepository, bg: Background, fg: Foreground) : BaseUsecase(bg, fg) { + + fun subscribe(done: (List) -> Unit, fail: (Throwable) -> Unit) { + onBackground({ + + val response = repo.subscribe { handle(it, done) } + handle(response, done) + + }, fail) + } + + fun unsubscribe() { + repo.unsubscribe() + } + + private fun handle(response: List, done: (List) -> Unit) { + val sort = response.sortedByDescending { it.viewed } + onUi { done(sort) } + } +} \ No newline at end of file diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/WtfItem.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/WtfItem.kt new file mode 100644 index 00000000..e941c62a --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/wtf/WtfItem.kt @@ -0,0 +1,10 @@ +package nl.entreco.domain.wtf + +data class WtfItem( + val docId: String, + val title: String, + val description: String, + val image: String?, + val video: String?, + val viewed: Long +) \ No newline at end of file diff --git a/android/DartsScorecard/scripts/dependencies.gradle b/android/DartsScorecard/scripts/dependencies.gradle index 4ac41c28..afa06cd5 100644 --- a/android/DartsScorecard/scripts/dependencies.gradle +++ b/android/DartsScorecard/scripts/dependencies.gradle @@ -59,6 +59,7 @@ ext { firebase = '15.0.0', glide = '4.7.1', circleImageView = '2.2.0', + jelly = 'v1.0', crash = '2.9.1@aar', gson = '2.8.2', leakCanary = '1.5.4',