mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-09 20:42:34 +01:00
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
This commit is contained in:
parent
041bafccea
commit
3076d9a97c
@ -172,6 +172,8 @@ dependencies {
|
||||
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:${GLIDE}"
|
||||
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.3"
|
||||
|
||||
//noinspection GradleDependency
|
||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${LEAK_CANARY}"
|
||||
//noinspection GradleDependency
|
||||
|
9
app/proguard-rules.pro
vendored
9
app/proguard-rules.pro
vendored
@ -28,4 +28,13 @@
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
# Jackson
|
||||
-keep @com.fasterxml.jackson.annotation.JsonIgnoreProperties class * { *; }
|
||||
-keep @com.fasterxml.jackson.annotation.JsonCreator class * { *; }
|
||||
-keep @com.fasterxml.jackson.annotation.JsonValue class * { *; }
|
||||
-keep class com.fasterxml.** { *; }
|
||||
-keepnames class com.fasterxml.jackson.** { *; }
|
||||
-keepclassmembers public final enum com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility {
|
||||
public static final com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility *;
|
||||
}
|
@ -16,7 +16,6 @@ import android.support.design.widget.CoordinatorLayout
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.design.widget.TabLayout
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.app.FragmentPagerAdapter
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
@ -58,6 +57,7 @@ import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
|
||||
import com.pitchedapps.frost.fragments.BaseFragment
|
||||
import com.pitchedapps.frost.fragments.WebFragment
|
||||
import com.pitchedapps.frost.parsers.FrostSearch
|
||||
import com.pitchedapps.frost.parsers.SearchParser
|
||||
import com.pitchedapps.frost.utils.*
|
||||
@ -80,7 +80,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
VideoViewHolder, SearchViewHolder,
|
||||
FrostBilling by IabMain() {
|
||||
|
||||
lateinit var adapter: SectionsPagerAdapter
|
||||
protected lateinit var adapter: SectionsPagerAdapter
|
||||
override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper)
|
||||
val toolbar: Toolbar by bindView(R.id.toolbar)
|
||||
val viewPager: FrostViewPager by bindView(R.id.container)
|
||||
@ -114,7 +114,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
controlWebview = WebView(this)
|
||||
setFrameContentView(Prefs.mainActivityLayout.layoutRes)
|
||||
setSupportActionBar(toolbar)
|
||||
adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs())
|
||||
adapter = SectionsPagerAdapter(loadFbTabs())
|
||||
viewPager.adapter = adapter
|
||||
viewPager.offscreenPageLimit = TAB_COUNT
|
||||
setupDrawer(savedInstanceState)
|
||||
@ -335,6 +335,19 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
}
|
||||
}
|
||||
|
||||
private val STATE_FORCE_FALLBACK = "frost_state_force_fallback"
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putStringArrayList(STATE_FORCE_FALLBACK, ArrayList(adapter.forcedFallbacks))
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
adapter.forcedFallbacks.clear()
|
||||
adapter.forcedFallbacks.addAll(savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK))
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
FbCookie.switchBackUser { }
|
||||
@ -384,32 +397,41 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
inline val currentFragment
|
||||
get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as BaseFragment
|
||||
|
||||
inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbItem>) : FragmentPagerAdapter(fm) {
|
||||
override fun reloadFragment(fragment: BaseFragment) {
|
||||
runOnUiThread { adapter.reloadFragment(fragment) }
|
||||
}
|
||||
|
||||
inner class SectionsPagerAdapter(val pages: List<FbItem>) : FragmentPagerAdapter(supportFragmentManager) {
|
||||
|
||||
val forcedFallbacks = mutableSetOf<String>()
|
||||
|
||||
fun reloadFragment(fragment: BaseFragment) {
|
||||
if (fragment is WebFragment) return
|
||||
L.d("Reload fragment ${fragment.position}: ${fragment.baseEnum.name}")
|
||||
forcedFallbacks.add(fragment.baseEnum.name)
|
||||
supportFragmentManager.beginTransaction().remove(fragment).commitNowAllowingStateLoss()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
val item = pages[position]
|
||||
val fragment = BaseFragment(item.fragmentCreator, item, position)
|
||||
//If first load hasn't occurred, add a listener
|
||||
// todo check
|
||||
// if (!firstLoadFinished) {
|
||||
// var disposable: Disposable? = null
|
||||
// fragment.post {
|
||||
// disposable = it.web.refreshObservable.subscribe {
|
||||
// if (!it) {
|
||||
// //Ensure first load finisher only happens once
|
||||
// if (!firstLoadFinished) firstLoadFinished = true
|
||||
// disposable?.dispose()
|
||||
// disposable = null
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return fragment
|
||||
return BaseFragment(item.fragmentCreator,
|
||||
forcedFallbacks.contains(item.name),
|
||||
item,
|
||||
position)
|
||||
}
|
||||
|
||||
override fun getCount() = pages.size
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence = getString(pages[position].titleId)
|
||||
|
||||
override fun getItemPosition(fragment: Any) =
|
||||
if (fragment !is BaseFragment)
|
||||
POSITION_UNCHANGED
|
||||
else if (fragment is WebFragment || fragment.valid)
|
||||
POSITION_UNCHANGED
|
||||
else
|
||||
POSITION_NONE
|
||||
}
|
||||
|
||||
override val lowerVideoPadding: PointF
|
||||
|
@ -3,12 +3,18 @@ package com.pitchedapps.frost.activities
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.TabLayout
|
||||
import android.support.v4.view.ViewPager
|
||||
import ca.allanwang.kau.utils.materialDialog
|
||||
import ca.allanwang.kau.utils.toast
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.facebook.requests.fbRequest
|
||||
import com.pitchedapps.frost.facebook.requests.getMenuData
|
||||
import com.pitchedapps.frost.views.BadgedIcon
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@ -16,13 +22,7 @@ class MainActivity : BaseMainActivity() {
|
||||
|
||||
override val fragmentSubject = PublishSubject.create<Int>()!!
|
||||
var lastPosition = -1
|
||||
val headerBadgeObservable = PublishSubject.create<String>()
|
||||
var firstLoadFinished = false
|
||||
set(value) {
|
||||
if (field && value) return //both vals are already true
|
||||
L.i("First fragment load has finished")
|
||||
field = value
|
||||
}
|
||||
val headerBadgeObservable = PublishSubject.create<String>()!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.pitchedapps.frost.contracts
|
||||
|
||||
import com.pitchedapps.frost.fragments.BaseFragment
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
|
||||
/**
|
||||
@ -12,4 +13,5 @@ interface MainActivityContract : ActivityContract {
|
||||
fun setTitle(res: Int)
|
||||
fun setTitle(text: CharSequence)
|
||||
fun collapseAppBar()
|
||||
fun reloadFragment(fragment: BaseFragment)
|
||||
}
|
@ -6,10 +6,7 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||
import com.mikepenz.iconics.typeface.IIcon
|
||||
import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.fragments.BaseFragment
|
||||
import com.pitchedapps.frost.fragments.NotificationFragment
|
||||
import com.pitchedapps.frost.fragments.WebFragment
|
||||
import com.pitchedapps.frost.fragments.WebFragmentMenu
|
||||
import com.pitchedapps.frost.fragments.*
|
||||
import com.pitchedapps.frost.utils.EnumBundle
|
||||
import com.pitchedapps.frost.utils.EnumBundleCompanion
|
||||
import com.pitchedapps.frost.utils.EnumCompanion
|
||||
@ -30,7 +27,7 @@ enum class FbItem(
|
||||
FEED_TOP_STORIES(R.string.top_stories, GoogleMaterial.Icon.gmd_star, "home.php?sk=h_nor"),
|
||||
FRIENDS(R.string.friends, GoogleMaterial.Icon.gmd_person_add, "friends/center/requests"),
|
||||
GROUPS(R.string.groups, GoogleMaterial.Icon.gmd_group, "groups"),
|
||||
MENU(R.string.menu, GoogleMaterial.Icon.gmd_menu, "settings", ::WebFragmentMenu),
|
||||
MENU(R.string.menu, GoogleMaterial.Icon.gmd_menu, "settings", ::MenuFragment),
|
||||
MESSAGES(R.string.messages, MaterialDesignIconic.Icon.gmi_comments, "messages"),
|
||||
NOTES(R.string.notes, CommunityMaterial.Icon.cmd_note, "notes"),
|
||||
NOTIFICATIONS(R.string.notifications, MaterialDesignIconic.Icon.gmi_globe, "notifications", ::NotificationFragment),
|
||||
|
@ -19,7 +19,8 @@ private val authMap: MutableMap<String, RequestAuth> = mutableMapOf()
|
||||
* [action] will only be called if a valid auth is found.
|
||||
* Otherwise, [fail] will be called
|
||||
*/
|
||||
fun String.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) {
|
||||
fun String?.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) {
|
||||
if (this == null) return fail()
|
||||
val savedAuth = authMap[this]
|
||||
if (savedAuth != null) {
|
||||
savedAuth.action()
|
||||
|
@ -0,0 +1,158 @@
|
||||
package com.pitchedapps.frost.facebook.requests
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.MapperFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.pitchedapps.frost.facebook.FB_URL_BASE
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import okhttp3.Call
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import org.jsoup.Jsoup
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 29/12/17.
|
||||
*/
|
||||
fun RequestAuth.getMenuData(): FrostRequest<MenuData?> {
|
||||
|
||||
val body = listOf(
|
||||
"fb_dtsg" to fb_dtsg,
|
||||
"__user" to userId
|
||||
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
|
||||
|
||||
return frostRequest(::parseMenu) {
|
||||
url("${FB_URL_BASE}bookmarks/flyout/body/?id=u_0_2")
|
||||
post(body.toForm())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun parseMenu(call: Call): MenuData? {
|
||||
val fullString = call.execute().body()?.string() ?: return null
|
||||
var jsonString = fullString.substringAfter("bookmarkGroups", "")
|
||||
.substringAfter("[", "")
|
||||
|
||||
if (jsonString.isBlank()) return null
|
||||
|
||||
jsonString = "{ \"data\" : [${StringEscapeUtils.unescapeEcmaScript(jsonString)}"
|
||||
|
||||
val mapper = ObjectMapper()
|
||||
.disable(MapperFeature.AUTO_DETECT_SETTERS)
|
||||
|
||||
return try {
|
||||
val data = mapper.readValue(jsonString, MenuData::class.java)
|
||||
|
||||
// parse footer content
|
||||
|
||||
val footer = fullString.substringAfter("footerMarkup", "")
|
||||
.substringAfter("{", "")
|
||||
.substringBefore("}", "")
|
||||
|
||||
val doc = Jsoup.parseBodyFragment(StringEscapeUtils.unescapeEcmaScript(
|
||||
StringEscapeUtils.unescapeEcmaScript(footer)))
|
||||
val footerData = mutableListOf<MenuFooterItem>()
|
||||
val footerSmallData = mutableListOf<MenuFooterItem>()
|
||||
|
||||
doc.select("a[href]").forEach {
|
||||
val text = it.text()
|
||||
it.parent()
|
||||
if (text.isEmpty()) return@forEach
|
||||
val href = it.attr("href").formattedFbUrl
|
||||
val item = MenuFooterItem(name = text, url = href)
|
||||
if (it.parent().tag().name == "span")
|
||||
footerSmallData.add(item)
|
||||
else
|
||||
footerData.add(item)
|
||||
}
|
||||
|
||||
return data.copy(footer = MenuFooter(footerData, footerSmallData))
|
||||
} catch (e: IOException) {
|
||||
L.e(e, "Menu parse fail")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class MenuData(val data: List<MenuHeader> = emptyList(),
|
||||
val footer: MenuFooter = MenuFooter()) {
|
||||
|
||||
@JsonCreator constructor(
|
||||
@JsonProperty("data") data: List<MenuHeader>?
|
||||
) : this(data ?: emptyList(), MenuFooter())
|
||||
|
||||
fun flatMapValid() : List<MenuItemData> {
|
||||
val items = mutableListOf<MenuItemData>()
|
||||
data.forEach {
|
||||
if (it.isValid) items.add(it)
|
||||
items.addAll(it.visible.filter(MenuItem::isValid))
|
||||
}
|
||||
|
||||
items.addAll(footer.data.filter(MenuFooterItem::isValid))
|
||||
items.addAll(footer.smallData.filter(MenuFooterItem::isValid))
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface MenuItemData {
|
||||
val isValid: Boolean
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class MenuHeader(val id: String? = null,
|
||||
val header: String? = null,
|
||||
val visible: List<MenuItem> = emptyList(),
|
||||
val all: List<MenuItem> = emptyList()) : MenuItemData {
|
||||
|
||||
@JsonCreator constructor(
|
||||
@JsonProperty("id") id: String?,
|
||||
@JsonProperty("header") header: String?,
|
||||
@JsonProperty("visible") visible: List<MenuItem>?,
|
||||
@JsonProperty("all") all: List<MenuItem>?,
|
||||
@JsonProperty("fake") fake: Boolean?
|
||||
) : this(id, header, visible ?: emptyList(), all ?: emptyList())
|
||||
|
||||
override val isValid: Boolean
|
||||
get() = header != null
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class MenuItem(val id: String? = null,
|
||||
val name: String? = null,
|
||||
val pic: String? = null,
|
||||
val url: String? = null,
|
||||
val count: Int = 0,
|
||||
val countDetails: String? = null) : MenuItemData {
|
||||
|
||||
@JsonCreator constructor(
|
||||
@JsonProperty("id") id: String?,
|
||||
@JsonProperty("name") name: String?,
|
||||
@JsonProperty("pic") pic: String?,
|
||||
@JsonProperty("url") url: String?,
|
||||
@JsonProperty("count") count: Int?,
|
||||
@JsonProperty("count_details") countDetails: String?,
|
||||
@JsonProperty("fake") fake: Boolean?
|
||||
) : this(id, name, pic?.formattedFbUrl, url?.formattedFbUrl, count ?: 0, countDetails)
|
||||
|
||||
override val isValid: Boolean
|
||||
get() = name != null && url != null
|
||||
}
|
||||
|
||||
data class MenuFooter(val data: List<MenuFooterItem> = emptyList(),
|
||||
val smallData: List<MenuFooterItem> = emptyList()) {
|
||||
|
||||
val hasContent
|
||||
get() = data.isNotEmpty() || smallData.isNotEmpty()
|
||||
|
||||
}
|
||||
|
||||
data class MenuFooterItem(val name: String? = null,
|
||||
val url: String? = null,
|
||||
val isSmall: Boolean = false) : MenuItemData {
|
||||
override val isValid: Boolean
|
||||
get() = name != null && url != null
|
||||
}
|
@ -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()
|
@ -15,6 +15,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]
|
||||
*/
|
||||
|
@ -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()
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.pitchedapps.frost.iitems
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import ca.allanwang.kau.iitems.KauIItem
|
||||
import ca.allanwang.kau.ui.createSimpleRippleDrawable
|
||||
import ca.allanwang.kau.utils.bindView
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.IAdapter
|
||||
import com.mikepenz.fastadapter.IItem
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.launchWebOverlay
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 30/12/17.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base contract for anything with a url that may be launched in a new overlay
|
||||
*/
|
||||
interface ClickableIItemContract {
|
||||
|
||||
val url: String?
|
||||
|
||||
fun click(context: Context) {
|
||||
val url = url ?: return
|
||||
context.launchWebOverlay(url)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun bindEvents(adapter: IAdapter<IItem<*, *>>) {
|
||||
adapter.fastAdapter.withSelectable(false)
|
||||
.withOnClickListener { v, _, item, _ ->
|
||||
if (item is ClickableIItemContract) {
|
||||
item.click(v.context)
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic header item
|
||||
* Not clickable with an accent color
|
||||
*/
|
||||
open class HeaderIItem(val text: String?,
|
||||
itemId: Int = R.layout.iitem_header)
|
||||
: KauIItem<HeaderIItem, HeaderIItem.ViewHolder>(R.layout.iitem_header, ::ViewHolder, itemId) {
|
||||
|
||||
class ViewHolder(itemView: View) : FastAdapter.ViewHolder<HeaderIItem>(itemView) {
|
||||
|
||||
val text: TextView by bindView(R.id.item_header_text)
|
||||
|
||||
override fun bindView(item: HeaderIItem, payloads: MutableList<Any>) {
|
||||
text.setTextColor(Prefs.accentColor)
|
||||
text.text = item.text
|
||||
text.setBackgroundColor(Prefs.nativeBgColor)
|
||||
}
|
||||
|
||||
override fun unbindView(item: HeaderIItem) {
|
||||
text.text = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic text item
|
||||
* Clickable with text color
|
||||
*/
|
||||
open class TextIItem(val text: String?,
|
||||
override val url: String?,
|
||||
itemId: Int = R.layout.iitem_text)
|
||||
: KauIItem<TextIItem, TextIItem.ViewHolder>(R.layout.iitem_text, ::ViewHolder, itemId),
|
||||
ClickableIItemContract {
|
||||
|
||||
class ViewHolder(itemView: View) : FastAdapter.ViewHolder<TextIItem>(itemView) {
|
||||
|
||||
val text: TextView by bindView(R.id.item_text_view)
|
||||
|
||||
override fun bindView(item: TextIItem, payloads: MutableList<Any>) {
|
||||
text.setTextColor(Prefs.textColor)
|
||||
text.text = item.text
|
||||
text.background = createSimpleRippleDrawable(Prefs.bgColor, Prefs.nativeBgColor)
|
||||
}
|
||||
|
||||
override fun unbindView(item: TextIItem) {
|
||||
text.text = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.pitchedapps.frost.iitems
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import ca.allanwang.kau.iitems.KauIItem
|
||||
import ca.allanwang.kau.ui.createSimpleRippleDrawable
|
||||
import ca.allanwang.kau.utils.bindView
|
||||
import ca.allanwang.kau.utils.gone
|
||||
import ca.allanwang.kau.utils.visible
|
||||
import com.bumptech.glide.Glide
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.facebook.requests.MenuFooterItem
|
||||
import com.pitchedapps.frost.facebook.requests.MenuHeader
|
||||
import com.pitchedapps.frost.facebook.requests.MenuItem
|
||||
import com.pitchedapps.frost.glide.FrostGlide
|
||||
import com.pitchedapps.frost.glide.transform
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 30/12/17.
|
||||
*/
|
||||
class MenuContentIItem(val data: MenuItem)
|
||||
: KauIItem<MenuContentIItem, MenuContentIItem.ViewHolder>(R.layout.iitem_menu, ::ViewHolder),
|
||||
ClickableIItemContract {
|
||||
|
||||
override val url: String?
|
||||
get() = data.url
|
||||
|
||||
class ViewHolder(itemView: View) : FastAdapter.ViewHolder<MenuContentIItem>(itemView) {
|
||||
|
||||
val frame: ViewGroup by bindView(R.id.item_frame)
|
||||
val icon: ImageView by bindView(R.id.item_icon)
|
||||
val content: TextView by bindView(R.id.item_content)
|
||||
val badge: TextView by bindView(R.id.item_badge)
|
||||
|
||||
override fun bindView(item: MenuContentIItem, payloads: MutableList<Any>) {
|
||||
frame.background = createSimpleRippleDrawable(Prefs.textColor, Prefs.nativeBgColor)
|
||||
content.setTextColor(Prefs.textColor)
|
||||
badge.setTextColor(Prefs.textColor)
|
||||
val iconUrl = item.data.pic
|
||||
if (iconUrl != null)
|
||||
Glide.with(itemView).load(iconUrl)
|
||||
.transform(FrostGlide.roundCorner)
|
||||
.into(icon.visible())
|
||||
else
|
||||
icon.gone()
|
||||
content.text = item.data.name
|
||||
}
|
||||
|
||||
override fun unbindView(item: MenuContentIItem) {
|
||||
badge.gone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MenuHeaderIItem(val data: MenuHeader) : HeaderIItem(data.header,
|
||||
itemId = R.id.item_menu_header)
|
||||
|
||||
class MenuFooterIItem(val data: MenuFooterItem)
|
||||
: TextIItem(data.name, data.url, R.id.item_menu_footer)
|
||||
|
||||
class MenuFooterSmallIItem(val data: MenuFooterItem)
|
||||
: TextIItem(data.name, data.url, R.id.item_menu_footer_small)
|
||||
|
@ -54,8 +54,7 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
|
||||
override fun bindView(item: NotificationIItem, payloads: MutableList<Any>) {
|
||||
val notif = item.notification
|
||||
frame.background = createSimpleRippleDrawable(Prefs.textColor,
|
||||
Prefs.bgColor.colorToForeground(if (notif.unread) 0.7f else 0.0f)
|
||||
.withAlpha(30))
|
||||
Prefs.nativeBgColor(notif.unread))
|
||||
content.setTextColor(Prefs.textColor)
|
||||
date.setTextColor(Prefs.textColor.withAlpha(150))
|
||||
|
||||
|
@ -64,7 +64,8 @@ private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
|
||||
|
||||
override fun parseImpl(doc: Document): FrostNotifs? {
|
||||
val notificationList = doc.getElementById("notifications_list") ?: return null
|
||||
val notifications = notificationList.getElementsByAttributeValueContaining("id", "list_notif_")
|
||||
val notifications = notificationList
|
||||
.getElementsByAttributeValueContaining("id", "list_notif_")
|
||||
.mapNotNull(this::parseNotif)
|
||||
val seeMore = parseLink(doc.getElementsByAttributeValue("href", "/notifications.php?more").first())
|
||||
return FrostNotifs(notifications, seeMore)
|
||||
|
@ -5,7 +5,9 @@ import ca.allanwang.kau.kotlin.lazyResettable
|
||||
import ca.allanwang.kau.kpref.KPref
|
||||
import ca.allanwang.kau.kpref.StringSet
|
||||
import ca.allanwang.kau.kpref.kpref
|
||||
import ca.allanwang.kau.utils.colorToForeground
|
||||
import ca.allanwang.kau.utils.isColorVisibleOn
|
||||
import ca.allanwang.kau.utils.withAlpha
|
||||
import com.pitchedapps.frost.enums.FACEBOOK_BLUE
|
||||
import com.pitchedapps.frost.enums.FeedSort
|
||||
import com.pitchedapps.frost.enums.MainActivityLayout
|
||||
@ -59,11 +61,18 @@ object Prefs : KPref() {
|
||||
val accentColor: Int
|
||||
get() = t.accentColor
|
||||
|
||||
val accentColorForWhite: Int
|
||||
inline val accentColorForWhite: Int
|
||||
get() = if (accentColor.isColorVisibleOn(Color.WHITE)) accentColor
|
||||
else if (textColor.isColorVisibleOn(Color.WHITE)) textColor
|
||||
else FACEBOOK_BLUE
|
||||
|
||||
inline val nativeBgColor: Int
|
||||
get() = Prefs.bgColor.withAlpha(30)
|
||||
|
||||
fun nativeBgColor(unread: Boolean) = Prefs.bgColor
|
||||
.colorToForeground(if (unread) 0.7f else 0.0f)
|
||||
.withAlpha(30)
|
||||
|
||||
val bgColor: Int
|
||||
get() = t.bgColor
|
||||
|
||||
@ -79,8 +88,8 @@ object Prefs : KPref() {
|
||||
val isCustomTheme: Boolean
|
||||
get() = t == Theme.CUSTOM
|
||||
|
||||
val frostId: String
|
||||
get() = "${installDate}-${identifier}"
|
||||
inline val frostId: String
|
||||
get() = "$installDate-$identifier"
|
||||
|
||||
var tintNavBar: Boolean by kpref("tint_nav_bar", true)
|
||||
|
||||
@ -150,10 +159,8 @@ object Prefs : KPref() {
|
||||
|
||||
var mainActivityLayoutType: Int by kpref("main_activity_layout_type", 0)
|
||||
|
||||
val mainActivityLayout: MainActivityLayout
|
||||
inline val mainActivityLayout: MainActivityLayout
|
||||
get() = MainActivityLayout(mainActivityLayoutType)
|
||||
|
||||
var nativeViews: Boolean by kpref("native_views", true)
|
||||
|
||||
override fun deleteKeys() = arrayOf("search_bar")
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class FrostChromeClient(web: FrostWebView) : WebChromeClient() {
|
||||
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
|
||||
if (consoleBlacklist.any { consoleMessage.message().contains(it) }) return true
|
||||
L.d("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}")
|
||||
L.v("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}")
|
||||
return true
|
||||
}
|
||||
|
||||
|
9
app/src/main/res/layout/iitem_header.xml
Normal file
9
app/src/main/res/layout/iitem_header.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/item_header_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/kau_activity_vertical_margin"
|
||||
android:paddingEnd="@dimen/kau_padding_small"
|
||||
android:paddingStart="@dimen/kau_activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/kau_padding_small" />
|
42
app/src/main/res/layout/iitem_menu.xml
Normal file
42
app/src/main/res/layout/iitem_menu.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/item_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/kau_padding_small"
|
||||
android:paddingStart="@dimen/kau_activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/kau_padding_small">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_icon"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/kau_activity_horizontal_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/kau_activity_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/kau_activity_horizontal_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/item_badge"
|
||||
app:layout_constraintStart_toEndOf="@id/item_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
11
app/src/main/res/layout/iitem_text.xml
Normal file
11
app/src/main/res/layout/iitem_text.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/item_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingBottom="@dimen/kau_activity_vertical_margin"
|
||||
android:paddingEnd="@dimen/kau_padding_small"
|
||||
android:paddingStart="@dimen/kau_activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/kau_padding_small" />
|
@ -3,6 +3,9 @@
|
||||
<item name="item_account" type="id" />
|
||||
<item name="item_keyword" type="id" />
|
||||
<item name="item_about_links" type="id" />
|
||||
<item name="item_menu_header" type="id" />
|
||||
<item name="item_menu_footer" type="id" />
|
||||
<item name="item_menu_footer_small" type="id" />
|
||||
|
||||
<item name="intro_phone" type="id" />
|
||||
<item name="intro_phone_screen" type="id" />
|
||||
|
@ -6,13 +6,19 @@
|
||||
<item text="" />
|
||||
-->
|
||||
|
||||
<version title="v1.7.4" />
|
||||
<item text="Mark notifications as read when clicked!" />
|
||||
<item text="Create menu parser" />
|
||||
<item text="Implement automatic web fallback" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
||||
<version title="v1.7.2" />
|
||||
<item text="Optimize login view" />
|
||||
<item text="Rewrite parsers" />
|
||||
<item text="Fix message notification icons" />
|
||||
<item text="Small theme updates" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
||||
<version title="v1.7.1" />
|
||||
<item text="Fix launching messages in new overlay" />
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.pitchedapps.frost.facebook
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.pitchedapps.frost.facebook.requests.getAuth
|
||||
import com.pitchedapps.frost.facebook.requests.getFullSizedImage
|
||||
import com.pitchedapps.frost.facebook.requests.getMenuData
|
||||
import com.pitchedapps.frost.facebook.requests.markNotificationRead
|
||||
import com.pitchedapps.frost.internal.AUTH
|
||||
import com.pitchedapps.frost.internal.COOKIE
|
||||
@ -59,4 +61,13 @@ class FbRequestTest {
|
||||
assertTrue(url?.startsWith("https://scontent") == true)
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
fun testMenu() {
|
||||
val data = AUTH.getMenuData().invoke()
|
||||
assertNotNull(data)
|
||||
println(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(data!!))
|
||||
assertTrue(data.footer.hasContent, "Footer may be badly parsed")
|
||||
val items = data.flatMapValid()
|
||||
assertTrue(items.size > 15, "Something may be badly parsed")
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.4
|
||||
* Mark notifications as read when clicked!
|
||||
* Create menu parser
|
||||
* Implement automatic web fallback
|
||||
|
||||
## v1.7.2
|
||||
* Optimize login view
|
||||
* Rewrite parsers
|
||||
|
Loading…
Reference in New Issue
Block a user