diff options
author | Allan Wang <me@allanwang.ca> | 2017-12-31 00:42:49 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-31 00:42:49 -0500 |
commit | 3076d9a97c203497aec1415d8ac6037d10eebb46 (patch) | |
tree | cdeb914fa95f2b230f6327be3e1527d15b41dc94 /app/src/main/kotlin/com/pitchedapps/frost/fragments | |
parent | 041bafcceadbd5203e95f2692899ac903dd2e883 (diff) | |
download | frost-3076d9a97c203497aec1415d8ac6037d10eebb46.tar.gz frost-3076d9a97c203497aec1415d8ac6037d10eebb46.tar.bz2 frost-3076d9a97c203497aec1415d8ac6037d10eebb46.zip |
feature/menu-parser (#582)
* Test menu parser
* Add menu fragment implementation
* Test proguard
* Clean up
* Use async
* Use invoke
* Try without proguard
* Try 2
* Add fallback logic
* Use normal notification event
* Add custom event flag
* Add rest of menu fragment data
* Ensure fallback works
* Update docs
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/fragments')
5 files changed, 219 insertions, 114 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt index 963d00bb..8ab775e0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt @@ -6,28 +6,15 @@ import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import ca.allanwang.kau.adapters.fastAdapter import ca.allanwang.kau.utils.withArguments -import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.adapters.ItemAdapter -import com.mikepenz.fastadapter_extensions.items.ProgressItem -import com.pitchedapps.frost.R import com.pitchedapps.frost.contracts.DynamicUiContract import com.pitchedapps.frost.contracts.FrostContentParent import com.pitchedapps.frost.contracts.MainActivityContract import com.pitchedapps.frost.enums.FeedSort -import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem -import com.pitchedapps.frost.parsers.FrostParser -import com.pitchedapps.frost.parsers.ParseResponse import com.pitchedapps.frost.utils.* -import com.pitchedapps.frost.views.FrostRecyclerView import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.toast -import org.jetbrains.anko.uiThread /** * Created by Allan Wang on 2017-11-07. @@ -39,9 +26,10 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract { companion object { private const val ARG_POSITION = "arg_position" + private const val ARG_VALID = "arg_valid" - internal operator fun invoke(base: () -> BaseFragment, data: FbItem, position: Int): BaseFragment { - val fragment = if (Prefs.nativeViews) base() else WebFragment() + internal operator fun invoke(base: () -> BaseFragment, useFallback: Boolean, data: FbItem, position: Int): BaseFragment { + val fragment = if (!useFallback) base() else WebFragment() val d = if (data == FbItem.FEED) FeedSort(Prefs.feedSort).item else data fragment.withArguments( ARG_URL to d.url, @@ -56,6 +44,17 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract { override val baseEnum: FbItem by lazy { FbItem[arguments]!! } override val position: Int by lazy { arguments!!.getInt(ARG_POSITION) } + override var valid: Boolean + get() = arguments!!.getBoolean(ARG_VALID, true) + set(value) { + if (value || this is WebFragment) return + arguments!!.putBoolean(ARG_VALID, value) + L.e("Invalidating position $position") + frostAnswersCustom("Native Fallback", + "Item" to baseEnum.name) + (context as MainActivityContract).reloadFragment(this) + } + override var firstLoad: Boolean = true private var activityDisposable: Disposable? = null private var onCreateRunnable: ((FragmentContract) -> Unit)? = null @@ -147,6 +146,7 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract { } override fun onDestroyView() { + L.i("Fragment on destroy $position ${hashCode()}") content?.destroy() content = null super.onDestroyView() @@ -175,92 +175,3 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract { override fun onTabClick(): Unit = content?.core?.onTabClicked() ?: Unit } -abstract class RecyclerFragment<T : Any, Item : IItem<*, *>> : BaseFragment(), RecyclerContentContract { - - override val layoutRes: Int = R.layout.view_content_recycler - - /** - * The parser to make this all happen - */ - abstract val parser: FrostParser<T> - - open fun getDoc(cookie: String?) = frostJsoup(cookie, parser.url) - - val adapter: ItemAdapter<Item> = ItemAdapter() - - abstract fun toItems(response: ParseResponse<T>): List<Item> - - override final fun bind(recyclerView: FrostRecyclerView) { - recyclerView.adapter = getAdapter() - recyclerView.onReloadClear = { adapter.clear() } - bindImpl(recyclerView) - } - - override fun firstLoadRequest() { - val core = core ?: return - if (firstLoad) { - core.reloadBase(true) - firstLoad = false - } - } - - /** - * Anything to call for one time bindings - * At this stage, all adapters will have FastAdapter references - */ - open fun bindImpl(recyclerView: FrostRecyclerView) = Unit - - /** - * Create the fast adapter to bind to the recyclerview - */ - open fun getAdapter(): FastAdapter<IItem<*, *>> = fastAdapter(this.adapter) - - override fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit) { - doAsync { - progress(10) - val cookie = FbCookie.webCookie - val doc = getDoc(cookie) - progress(60) - val response = parser.parse(cookie, doc) - if (response == null) { - uiThread { context?.toast(R.string.error_generic) } - L.eThrow("RecyclerFragment failed for ${baseEnum.name}") - Prefs.nativeViews = false - return@doAsync callback(false) - } - progress(80) - val items = toItems(response) - progress(97) - uiThread { adapter.setNewList(items) } - callback(true) - } - } -} - -//abstract class PagedRecyclerFragment<T : Any, Item : IItem<*, *>> : RecyclerFragment<T, Item>() { -// -// var allowPagedLoading = true -// -// val footerAdapter = ItemAdapter<FrostProgress>() -// -// val footerScrollListener = object : EndlessRecyclerOnScrollListener(footerAdapter) { -// override fun onLoadMore(currentPage: Int) { -// TODO("not implemented") -// -// } -// -// } -// -// override fun getAdapter() = fastAdapter(adapter, footerAdapter) -// -// override fun bindImpl(recyclerView: FrostRecyclerView) { -// recyclerView.addOnScrollListener(footerScrollListener) -// } -// -// override fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit) { -// footerScrollListener. -// super.reload(progress, callback) -// } -//} - -class FrostProgress : ProgressItem()
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt index a78eb0d0..98a081e6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt @@ -16,6 +16,13 @@ interface FragmentContract : FrostContentContainer { val content: FrostContentParent? /** + * Defines whether the fragment is valid in the viewpager + * Or if it needs to be recreated + * May be called from any thread to toggle status + */ + var valid: Boolean + + /** * Helper to retrieve the core from [content] */ val core: FrostContentCore? diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt new file mode 100644 index 00000000..c490de60 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt @@ -0,0 +1,149 @@ +package com.pitchedapps.frost.fragments + +import ca.allanwang.kau.adapters.fastAdapter +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.adapters.ModelAdapter +import com.mikepenz.fastadapter_extensions.items.ProgressItem +import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.parsers.FrostParser +import com.pitchedapps.frost.parsers.ParseResponse +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.frostJsoup +import com.pitchedapps.frost.views.FrostRecyclerView +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.toast +import org.jetbrains.anko.uiThread + +/** + * Created by Allan Wang on 27/12/17. + */ +abstract class RecyclerFragment : BaseFragment(), RecyclerContentContract { + + override val layoutRes: Int = R.layout.view_content_recycler + + override fun firstLoadRequest() { + val core = core ?: return + if (firstLoad) { + core.reloadBase(true) + firstLoad = false + } + } + + override final fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit) { + reloadImpl(progress) { + if (it) + callback(it) + else + valid = false + } + } + + protected abstract fun reloadImpl(progress: (Int) -> Unit, callback: (Boolean) -> Unit) +} + +abstract class GenericRecyclerFragment<T, Item : IItem<*, *>> : RecyclerFragment() { + + abstract fun mapper(data: T): Item + + val adapter: ModelAdapter<T, Item> = ModelAdapter(this::mapper) + + override final fun bind(recyclerView: FrostRecyclerView) { + recyclerView.adapter = getAdapter() + recyclerView.onReloadClear = { adapter.clear() } + bindImpl(recyclerView) + } + + /** + * Anything to call for one time bindings + * At this stage, all adapters will have FastAdapter references + */ + open fun bindImpl(recyclerView: FrostRecyclerView) = Unit + + /** + * Create the fast adapter to bind to the recyclerview + */ + open fun getAdapter(): FastAdapter<IItem<*, *>> = fastAdapter(this.adapter) + +} + +abstract class FrostParserFragment<T : Any, Item : IItem<*, *>> : RecyclerFragment() { + + /** + * The parser to make this all happen + */ + abstract val parser: FrostParser<T> + + open fun getDoc(cookie: String?) = frostJsoup(cookie, parser.url) + + abstract fun toItems(response: ParseResponse<T>): List<Item> + + val adapter: ItemAdapter<Item> = ItemAdapter() + + override final fun bind(recyclerView: FrostRecyclerView) { + recyclerView.adapter = getAdapter() + recyclerView.onReloadClear = { adapter.clear() } + bindImpl(recyclerView) + } + + /** + * Anything to call for one time bindings + * At this stage, all adapters will have FastAdapter references + */ + open fun bindImpl(recyclerView: FrostRecyclerView) = Unit + + /** + * Create the fast adapter to bind to the recyclerview + */ + open fun getAdapter(): FastAdapter<IItem<*, *>> = fastAdapter(this.adapter) + + override fun reloadImpl(progress: (Int) -> Unit, callback: (Boolean) -> Unit) { + doAsync { + progress(10) + val cookie = FbCookie.webCookie + val doc = getDoc(cookie) + progress(60) + val response = parser.parse(cookie, doc) + if (response == null) { + L.eThrow("RecyclerFragment failed for ${baseEnum.name}") + return@doAsync callback(false) + } + progress(80) + val items = toItems(response) + progress(97) + uiThread { adapter.setNewList(items) } + callback(true) + } + } +} + +//abstract class PagedRecyclerFragment<T : Any, Item : IItem<*, *>> : RecyclerFragment<T, Item>() { +// +// var allowPagedLoading = true +// +// val footerAdapter = ItemAdapter<FrostProgress>() +// +// val footerScrollListener = object : EndlessRecyclerOnScrollListener(footerAdapter) { +// override fun onLoadMore(currentPage: Int) { +// TODO("not implemented") +// +// } +// +// } +// +// override fun getAdapter() = fastAdapter(adapter, footerAdapter) +// +// override fun bindImpl(recyclerView: FrostRecyclerView) { +// recyclerView.addOnScrollListener(footerScrollListener) +// } +// +// override fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit) { +// footerScrollListener. +// super.reload(progress, callback) +// } +//} + +class FrostProgress : ProgressItem() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt index 4d4a6f8b..ca2912e8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt @@ -1,17 +1,22 @@ package com.pitchedapps.frost.fragments +import com.mikepenz.fastadapter.IItem +import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem -import com.pitchedapps.frost.iitems.NotificationIItem +import com.pitchedapps.frost.facebook.requests.* +import com.pitchedapps.frost.iitems.* import com.pitchedapps.frost.parsers.FrostNotifs import com.pitchedapps.frost.parsers.NotifParser import com.pitchedapps.frost.parsers.ParseResponse import com.pitchedapps.frost.utils.frostJsoup import com.pitchedapps.frost.views.FrostRecyclerView +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread /** * Created by Allan Wang on 27/12/17. */ -class NotificationFragment : RecyclerFragment<FrostNotifs, NotificationIItem>() { +class NotificationFragment : FrostParserFragment<FrostNotifs, NotificationIItem>() { override val parser = NotifParser @@ -23,5 +28,37 @@ class NotificationFragment : RecyclerFragment<FrostNotifs, NotificationIItem>() override fun bindImpl(recyclerView: FrostRecyclerView) { NotificationIItem.bindEvents(adapter) } +} +class MenuFragment : GenericRecyclerFragment<MenuItemData, IItem<*, *>>() { + + override fun mapper(data: MenuItemData): IItem<*, *> = when (data) { + is MenuHeader -> MenuHeaderIItem(data) + is MenuItem -> MenuContentIItem(data) + is MenuFooterItem -> + if (data.isSmall) MenuFooterSmallIItem(data) + else MenuFooterIItem(data) + else -> throw IllegalArgumentException("Menu item in fragment has invalid type ${data::class.java.simpleName}") + } + + override fun bindImpl(recyclerView: FrostRecyclerView) { + ClickableIItemContract.bindEvents(adapter) + } + + override fun reloadImpl(progress: (Int) -> Unit, callback: (Boolean) -> Unit) { + doAsync { + val cookie = FbCookie.webCookie + progress(10) + cookie.fbRequest({ callback(false) }) { + progress(30) + val data = getMenuData().invoke() ?: return@fbRequest callback(false) + if (data.data.isEmpty()) return@fbRequest callback(false) + progress(70) + val items = data.flatMapValid() + progress(90) + uiThread { adapter.add(items) } + callback(true) + } + } + } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt index 2740a36f..cdeea064 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt @@ -1,26 +1,27 @@ package com.pitchedapps.frost.fragments import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.views.FrostWebView import com.pitchedapps.frost.web.FrostWebViewClient import com.pitchedapps.frost.web.FrostWebViewClientMenu /** * Created by Allan Wang on 27/12/17. + * + * Basic webfragment + * Do not extend as this is always a fallback */ -open class WebFragment : BaseFragment() { +class WebFragment : BaseFragment() { override val layoutRes: Int = R.layout.view_content_web /** * Given a webview, output a client */ - open fun client(web: FrostWebView) = FrostWebViewClient(web) - -} - -class WebFragmentMenu : WebFragment() { - - override fun client(web: FrostWebView) = FrostWebViewClientMenu(web) + fun client(web: FrostWebView) = when (baseEnum) { + FbItem.MENU -> FrostWebViewClientMenu(web) + else -> FrostWebViewClient(web) + } }
\ No newline at end of file |