1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-09-16 20:12:25 +02:00

Enhancement/ktlint (#1259)

* Add spotless

* Reformat code

* Apply license header

* Add remaining license headers
This commit is contained in:
Allan Wang 2018-12-24 01:47:03 -05:00 committed by GitHub
parent c45b30e28f
commit 697d01882b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 3864 additions and 1348 deletions

View File

@ -6,6 +6,8 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'com.getkeepsafe.dexcount'
apply plugin: 'com.gladed.androidgitversion'
apply from: '../spotless.gradle'
android {
compileSdkVersion kau.targetSdk
buildToolsVersion kau.buildTools

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost
import android.app.Activity
@ -21,7 +37,11 @@ import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.services.setupNotificationChannels
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.BuildUtils
import com.pitchedapps.frost.utils.FrostPglAdBlock
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.Showcase
import com.raizlabs.android.dbflow.config.DatabaseConfig
import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager
@ -30,10 +50,9 @@ import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.*
import java.util.Random
import kotlin.reflect.KClass
/**
* Created by Allan Wang on 2017-05-28.
*/
@ -46,10 +65,12 @@ class FrostApp : Application() {
// lateinit var refWatcher: RefWatcher
private fun FlowConfig.Builder.withDatabase(name: String, klass: KClass<*>) =
addDatabaseConfig(DatabaseConfig.builder(klass.java)
.databaseName(name)
.modelNotifier(ContentResolverNotifier("${BuildConfig.APPLICATION_ID}.dbflow.provider"))
.build())
addDatabaseConfig(
DatabaseConfig.builder(klass.java)
.databaseName(name)
.modelNotifier(ContentResolverNotifier("${BuildConfig.APPLICATION_ID}.dbflow.provider"))
.build()
)
override fun onCreate() {
if (!buildIsLollipopAndUp) { // not supported
@ -57,11 +78,13 @@ class FrostApp : Application() {
return
}
FlowManager.init(FlowConfig.Builder(this)
FlowManager.init(
FlowConfig.Builder(this)
.withDatabase(CookiesDb.NAME, CookiesDb::class)
.withDatabase(FbTabsDb.NAME, FbTabsDb::class)
.withDatabase(NotificationDb.NAME, NotificationDb::class)
.build())
.build()
)
Showcase.initialize(this, "${BuildConfig.APPLICATION_ID}.showcase")
Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs")
// if (LeakCanary.isInAnalyzerProcess(this)) return
@ -95,9 +118,11 @@ class FrostApp : Application() {
val c = imageView.context
val request = GlideApp.with(c)
val old = request.load(uri).apply(RequestOptions().placeholder(placeholder))
request.load(uri).apply(RequestOptions()
.signature(ApplicationVersionSignature.obtain(c)))
.thumbnail(old).into(imageView)
request.load(uri).apply(
RequestOptions()
.signature(ApplicationVersionSignature.obtain(c))
)
.thumbnail(old).into(imageView)
}
})
if (BuildConfig.DEBUG)
@ -127,7 +152,6 @@ class FrostApp : Application() {
L.e(it) { "RxJava error" }
}
}
}
private fun initBugsnag() {
@ -136,7 +160,7 @@ class FrostApp : Application() {
Bugsnag.disableExceptionHandler()
if (!BuildConfig.APPLICATION_ID.startsWith("com.pitchedapps.frost")) return
val version = BuildUtils.match(BuildConfig.VERSION_NAME)
?: return L.d { "Bugsnag disabled for ${BuildConfig.VERSION_NAME}" }
?: return L.d { "Bugsnag disabled for ${BuildConfig.VERSION_NAME}" }
Bugsnag.enableExceptionHandler()
Bugsnag.setNotifyReleaseStages(*BuildUtils.getAllStages())
Bugsnag.setAppVersion(version.versionName)
@ -157,5 +181,4 @@ class FrostApp : Application() {
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost
import android.content.Intent
@ -21,7 +37,8 @@ import com.pitchedapps.frost.utils.EXTRA_COOKIES
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.launchNewTask
import java.util.*
import java.util.ArrayList
import java.util.IllegalFormatException
/**
* Created by Allan Wang on 2017-05-28.
@ -46,7 +63,8 @@ class StartActivity : KauBaseActivity() {
if (Prefs.userId != -1L)
startActivity<MainActivity>(intentBuilder = {
putParcelableArrayListExtra(EXTRA_COOKIES, cookies)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP
})
else
launchNewTask<SelectorActivity>(cookies)
@ -57,11 +75,10 @@ class StartActivity : KauBaseActivity() {
} catch (e: Exception) {
showInvalidWebView()
}
}
private fun showInvalidWebView() =
showInvalidView(R.string.error_webview)
showInvalidView(R.string.error_webview)
private fun showInvalidSdkView() {
val text = try {
@ -73,12 +90,12 @@ class StartActivity : KauBaseActivity() {
}
private fun showInvalidView(textRes: Int) =
showInvalidView(string(textRes))
showInvalidView(string(textRes))
private fun showInvalidView(text: String) {
setContentView(R.layout.activity_invalid)
findViewById<ImageView>(R.id.invalid_icon)
.setIcon(GoogleMaterial.Icon.gmd_adb, -1, Color.WHITE)
.setIcon(GoogleMaterial.Icon.gmd_adb, -1, Color.WHITE)
findViewById<TextView>(R.id.invalid_text).text = text
}
}
}

View File

@ -1,17 +1,40 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.about.AboutActivityBase
import ca.allanwang.kau.about.LibraryIItem
import ca.allanwang.kau.adapters.FastItemThemedAdapter
import ca.allanwang.kau.adapters.ThemableIItem
import ca.allanwang.kau.adapters.ThemableIItemDelegate
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.dimenPixelSize
import ca.allanwang.kau.utils.resolveDrawable
import ca.allanwang.kau.utils.startLink
import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.toDrawable
import ca.allanwang.kau.utils.toast
import ca.allanwang.kau.utils.withMinAlpha
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library
import com.mikepenz.aboutlibraries.entity.License
@ -25,7 +48,6 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
/**
* Created by Allan Wang on 2017-06-26.
*/
@ -42,21 +64,21 @@ class AboutActivity : AboutActivityBase(null, {
override fun getLibraries(libs: Libs): List<Library> {
val include = arrayOf(
"AboutLibraries",
"AndroidIconics",
"androidin_appbillingv3",
"androidslidinguppanel",
"Crashlytics",
"dbflow",
"fastadapter",
"glide",
"Jsoup",
"kau",
"kotterknife",
"materialdialogs",
"materialdrawer",
"rxjava",
"subsamplingscaleimageview"
"AboutLibraries",
"AndroidIconics",
"androidin_appbillingv3",
"androidslidinguppanel",
"Crashlytics",
"dbflow",
"fastadapter",
"glide",
"Jsoup",
"kau",
"kotterknife",
"materialdialogs",
"materialdrawer",
"rxjava",
"subsamplingscaleimageview"
)
val l = libs.prepareLibraries(this, include, null, false, true, true)
@ -136,11 +158,11 @@ class AboutActivity : AboutActivityBase(null, {
val c = itemView.context
val size = c.dimenPixelSize(R.dimen.kau_avatar_bounds)
images = arrayOf<Pair<IIcon, () -> Unit>>(
GoogleMaterial.Icon.gmd_arrow_downward to { c.startLink(R.string.github_downloads_url) },
CommunityMaterial.Icon2.cmd_reddit to { c.startLink(R.string.reddit_url) },
CommunityMaterial.Icon.cmd_github_circle to { c.startLink(R.string.github_url) },
CommunityMaterial.Icon2.cmd_slack to { c.startLink(R.string.slack_url) },
CommunityMaterial.Icon2.cmd_xda to { c.startLink(R.string.xda_url) }
GoogleMaterial.Icon.gmd_arrow_downward to { c.startLink(R.string.github_downloads_url) },
CommunityMaterial.Icon2.cmd_reddit to { c.startLink(R.string.reddit_url) },
CommunityMaterial.Icon.cmd_github_circle to { c.startLink(R.string.github_url) },
CommunityMaterial.Icon2.cmd_slack to { c.startLink(R.string.slack_url) },
CommunityMaterial.Icon2.cmd_xda to { c.startLink(R.string.xda_url) }
).mapIndexed { i, (icon, onClick) ->
ImageView(c).apply {
layoutParams = ViewGroup.LayoutParams(size, size)
@ -154,10 +176,16 @@ class AboutActivity : AboutActivityBase(null, {
}
val set = ConstraintSet()
set.clone(container)
set.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT,
images.map { it.id }.toIntArray(), null, ConstraintSet.CHAIN_SPREAD_INSIDE)
set.createHorizontalChain(ConstraintSet.PARENT_ID,
ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
ConstraintSet.RIGHT,
images.map { it.id }.toIntArray(),
null,
ConstraintSet.CHAIN_SPREAD_INSIDE
)
set.applyTo(container)
}
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.content.res.Configuration
@ -80,7 +96,6 @@ abstract class BaseActivity : KauBaseActivity() {
//// disposeNetworkConnectivity()
// }
override fun onStop() {
if (this is VideoViewHolder) videoOnStop()
super.onStop()
@ -90,4 +105,4 @@ abstract class BaseActivity : KauBaseActivity() {
super.onConfigurationChanged(newConfig)
if (this is VideoViewHolder) videoViewer?.updateLocation()
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.annotation.SuppressLint
@ -8,25 +24,31 @@ import android.graphics.PointF
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import androidx.annotation.StringRes
import com.google.android.material.appbar.AppBarLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.tabs.TabLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.appcompat.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.widget.FrameLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import ca.allanwang.kau.searchview.SearchItem
import ca.allanwang.kau.searchview.SearchView
import ca.allanwang.kau.searchview.SearchViewHolder
import ca.allanwang.kau.searchview.bindSearchView
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.fadeScaleTransition
import ca.allanwang.kau.utils.restart
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.setMenuIcons
import ca.allanwang.kau.utils.showIf
import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.toast
import ca.allanwang.kau.utils.withMinAlpha
import co.zsmb.materialdrawerkt.builders.Builder
import co.zsmb.materialdrawerkt.builders.accountHeader
import co.zsmb.materialdrawerkt.builders.drawer
@ -35,6 +57,9 @@ import co.zsmb.materialdrawerkt.draweritems.badgeable.secondaryItem
import co.zsmb.materialdrawerkt.draweritems.divider
import co.zsmb.materialdrawerkt.draweritems.profile.profile
import co.zsmb.materialdrawerkt.draweritems.profile.profileSetting
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.tabs.TabLayout
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.IIcon
@ -57,7 +82,26 @@ import com.pitchedapps.frost.facebook.parsers.SearchParser
import com.pitchedapps.frost.facebook.profilePictureUrl
import com.pitchedapps.frost.fragments.BaseFragment
import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.ACTIVITY_SETTINGS
import com.pitchedapps.frost.utils.EXTRA_COOKIES
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.MAIN_TIMEOUT_DURATION
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.REQUEST_NAV
import com.pitchedapps.frost.utils.REQUEST_REFRESH
import com.pitchedapps.frost.utils.REQUEST_RESTART
import com.pitchedapps.frost.utils.REQUEST_RESTART_APPLICATION
import com.pitchedapps.frost.utils.REQUEST_SEARCH
import com.pitchedapps.frost.utils.REQUEST_TEXT_ZOOM
import com.pitchedapps.frost.utils.cookies
import com.pitchedapps.frost.utils.frostChangelog
import com.pitchedapps.frost.utils.frostEvent
import com.pitchedapps.frost.utils.frostNavigationBar
import com.pitchedapps.frost.utils.launchLogin
import com.pitchedapps.frost.utils.launchNewTask
import com.pitchedapps.frost.utils.launchWebOverlay
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostColors
import com.pitchedapps.frost.views.BadgedIcon
import com.pitchedapps.frost.views.FrostVideoViewer
import com.pitchedapps.frost.views.FrostViewPager
@ -68,8 +112,8 @@ import com.pitchedapps.frost.views.FrostViewPager
* Most of the logic that is unrelated to handling fragments
*/
abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
FileChooserContract by FileChooserDelegate(),
VideoViewHolder, SearchViewHolder {
FileChooserContract by FileChooserDelegate(),
VideoViewHolder, SearchViewHolder {
protected lateinit var adapter: SectionsPagerAdapter
override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper)
@ -111,12 +155,14 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
Prefs.versionCode = BuildConfig.VERSION_CODE
if (!BuildConfig.DEBUG) {
frostChangelog()
frostEvent("Version",
"Version code" to BuildConfig.VERSION_CODE,
"Prev version code" to Prefs.prevVersionCode,
"Version name" to BuildConfig.VERSION_NAME,
"Build type" to BuildConfig.BUILD_TYPE,
"Frost id" to Prefs.frostId)
frostEvent(
"Version",
"Version code" to BuildConfig.VERSION_CODE,
"Prev version code" to Prefs.prevVersionCode,
"Version name" to BuildConfig.VERSION_NAME,
"Build type" to BuildConfig.BUILD_TYPE,
"Frost id" to Prefs.frostId
)
}
}
setupDrawer(savedInstanceState)
@ -204,7 +250,9 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
identifier = -2L
}
profileSetting(nameRes = R.string.kau_add_account) {
iconDrawable = IconicsDrawable(this@BaseMainActivity, GoogleMaterial.Icon.gmd_add).actionBar().paddingDp(5).color(Prefs.textColor)
iconDrawable =
IconicsDrawable(this@BaseMainActivity, GoogleMaterial.Icon.gmd_add).actionBar().paddingDp(5)
.color(Prefs.textColor)
textColor = Prefs.textColor.toLong()
identifier = -3L
}
@ -225,8 +273,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
} else {
materialDialogThemed {
title(R.string.kau_logout)
content(String.format(string(R.string.kau_logout_confirm_as_x), currentCookie.name
?: Prefs.userId.toString()))
content(
String.format(
string(R.string.kau_logout_confirm_as_x), currentCookie.name
?: Prefs.userId.toString()
)
)
positiveText(R.string.kau_yes)
negativeText(R.string.kau_no)
onPositive { _, _ -> FbCookie.logout(this@BaseMainActivity) }
@ -295,9 +347,11 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
toolbar.tint(Prefs.iconColor)
setMenuIcons(menu, Prefs.iconColor,
R.id.action_settings to GoogleMaterial.Icon.gmd_settings,
R.id.action_search to GoogleMaterial.Icon.gmd_search)
setMenuIcons(
menu, Prefs.iconColor,
R.id.action_settings to GoogleMaterial.Icon.gmd_settings,
R.id.action_search to GoogleMaterial.Icon.gmd_search
)
searchViewBindIfNull {
bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
textCallback = { query, searchView ->
@ -309,7 +363,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
if (data != null) {
val items = data.mapTo(mutableListOf(), FrostSearch::toSearchItem)
if (items.isNotEmpty())
items.add(SearchItem("${FbItem._SEARCH.url}?q=$query", string(R.string.show_all_results), iicon = null))
items.add(
SearchItem(
"${FbItem._SEARCH.url}?q=$query",
string(R.string.show_all_results),
iicon = null
)
)
searchViewCache[query] = items
searchView.results = items
}
@ -332,7 +392,8 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
R.id.action_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
intent.putParcelableArrayListExtra(EXTRA_COOKIES, cookies())
val bundle = ActivityOptions.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle()
val bundle =
ActivityOptions.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle()
startActivityForResult(intent, ACTIVITY_SETTINGS, bundle)
}
else -> return super.onOptionsItemSelected(item)
@ -340,7 +401,10 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
return true
}
override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams) {
override fun openFileChooser(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
) {
openMediaPicker(filePathCallback, fileChooserParams)
}
@ -377,8 +441,10 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
adapter.forcedFallbacks.clear()
adapter.forcedFallbacks.addAll(savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK)
?: emptyList())
adapter.forcedFallbacks.addAll(
savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK)
?: emptyList()
)
}
override fun onResume() {
@ -444,10 +510,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun getItem(position: Int): Fragment {
val item = pages[position]
return BaseFragment(item.fragmentCreator,
forcedFallbacks.contains(item.name),
item,
position)
return BaseFragment(
item.fragmentCreator,
forcedFallbacks.contains(item.name),
item,
position
)
}
override fun getCount() = pages.size
@ -455,12 +523,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
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
if (fragment !is BaseFragment)
POSITION_UNCHANGED
else if (fragment is WebFragment || fragment.valid)
POSITION_UNCHANGED
else
POSITION_NONE
}
override val lowerVideoPadding: PointF
@ -469,4 +537,4 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
PointF(0f, toolbar.height.toFloat())
else
PointF(0f, 0f)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.app.Activity
@ -9,9 +25,9 @@ import ca.allanwang.kau.internal.KauBaseActivity
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.visible
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.injectors.JsActions
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.createFreshDir
@ -73,23 +89,22 @@ class DebugActivity : KauBaseActivity() {
val body = it[1] as? String
screenshot to body
}.observeOn(AndroidSchedulers.mainThread())
.subscribe { (screenshot, body), err ->
if (err != null) {
L.e { "DebugActivity error ${err.message}" }
setResult(Activity.RESULT_CANCELED)
finish()
return@subscribe
}
val intent = Intent()
intent.putExtra(RESULT_URL, debug_webview.url)
intent.putExtra(RESULT_SCREENSHOT, screenshot)
if (body != null)
intent.putExtra(RESULT_BODY, body)
setResult(Activity.RESULT_OK, intent)
.subscribe { (screenshot, body), err ->
if (err != null) {
L.e { "DebugActivity error ${err.message}" }
setResult(Activity.RESULT_CANCELED)
finish()
return@subscribe
}
val intent = Intent()
intent.putExtra(RESULT_URL, debug_webview.url)
intent.putExtra(RESULT_SCREENSHOT, screenshot)
if (body != null)
intent.putExtra(RESULT_BODY, body)
setResult(Activity.RESULT_OK, intent)
finish()
}
}
}
override fun onSupportNavigateUp(): Boolean {
@ -113,4 +128,4 @@ class DebugActivity : KauBaseActivity() {
else
super.onBackPressed()
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.content.Intent
@ -5,16 +21,25 @@ import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.os.Environment
import com.google.android.material.floatingactionbutton.FloatingActionButton
import android.view.View
import ca.allanwang.kau.internal.KauBaseActivity
import ca.allanwang.kau.logging.KauLoggerExtension
import ca.allanwang.kau.mediapicker.scanMedia
import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.fadeOut
import ca.allanwang.kau.utils.fadeScaleTransition
import ca.allanwang.kau.utils.isHidden
import ca.allanwang.kau.utils.scaleXY
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.use
import ca.allanwang.kau.utils.withAlpha
import ca.allanwang.kau.utils.withMinAlpha
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.R
@ -23,7 +48,19 @@ import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.facebook.requests.call
import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl
import com.pitchedapps.frost.facebook.requests.requestBuilder
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.ARG_COOKIE
import com.pitchedapps.frost.utils.ARG_IMAGE_URL
import com.pitchedapps.frost.utils.ARG_TEXT
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.createFreshFile
import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.frostUriFromFile
import com.pitchedapps.frost.utils.isIndirectImageUrl
import com.pitchedapps.frost.utils.logFrostEvent
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.sendFrostEmail
import com.pitchedapps.frost.utils.setFrostColors
import com.sothree.slidinguppanel.SlidingUpPanelLayout
import kotlinx.android.synthetic.main.activity_image.*
import okhttp3.Response
@ -34,7 +71,8 @@ import java.io.File
import java.io.FileFilter
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Locale
/**
* Created by Allan Wang on 2017-07-15.
@ -94,8 +132,10 @@ class ImageActivity : KauBaseActivity() {
// a unique image identifier based on the id (if it exists), and its hash
private val imageHash: String by lazy {
"${Math.abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode()
?: 0)}_${Math.abs(imageUrl.hashCode())}"
"${Math.abs(
FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode()
?: 0
)}_${Math.abs(imageUrl.hashCode())}"
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -105,11 +145,15 @@ class ImageActivity : KauBaseActivity() {
L.v { "Displaying image $imageUrl" }
val layout = if (!imageText.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless
setContentView(layout)
image_container.setBackgroundColor(if (Prefs.blackMediaBg) Color.BLACK
else Prefs.bgColor.withMinAlpha(222))
image_container.setBackgroundColor(
if (Prefs.blackMediaBg) Color.BLACK
else Prefs.bgColor.withMinAlpha(222)
)
image_text?.setTextColor(if (Prefs.blackMediaBg) Color.WHITE else Prefs.textColor)
image_text?.setBackgroundColor((if (Prefs.blackMediaBg) Color.BLACK else Prefs.bgColor)
.colorToForeground(0.2f).withAlpha(255))
image_text?.setBackgroundColor(
(if (Prefs.blackMediaBg) Color.BLACK else Prefs.bgColor)
.colorToForeground(0.2f).withAlpha(255)
)
image_text?.text = imageText
image_progress.tint(if (Prefs.blackMediaBg) Color.WHITE else Prefs.accentColor)
image_panel?.addPanelSlideListener(object : SlidingUpPanelLayout.SimplePanelSlideListener() {
@ -208,16 +252,15 @@ class ImageActivity : KauBaseActivity() {
}
private fun getImageResponse(): Response = cookie.requestBuilder()
.url(trueImageUrl)
.get()
.call()
.execute()
.url(trueImageUrl)
.get()
.call()
.execute()
@Throws(IOException::class)
private fun downloadImageTo(file: File) {
val body = getImageResponse().body()
?: throw IOException("Failed to retrieve image body")
?: throw IOException("Failed to retrieve image body")
body.byteStream().use { input ->
file.outputStream().use { output ->
input.copyTo(output)
@ -272,7 +315,11 @@ class ImageActivity : KauBaseActivity() {
}
}
internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconColor, val backgroundTint: Int = Int.MAX_VALUE) {
internal enum class FabStates(
val iicon: IIcon,
val iconColor: Int = Prefs.iconColor,
val backgroundTint: Int = Int.MAX_VALUE
) {
ERROR(GoogleMaterial.Icon.gmd_error, Color.WHITE, Color.RED) {
override fun onClick(activity: ImageActivity) {
activity.materialDialogThemed {
@ -334,5 +381,4 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
}
abstract fun onClick(activity: ImageActivity)
}
}

View File

@ -1,31 +1,60 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import android.view.View
import android.view.WindowManager
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import ca.allanwang.kau.internal.KauBaseActivity
import ca.allanwang.kau.ui.views.RippleCanvas
import ca.allanwang.kau.ui.widgets.InkPageIndicator
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.blendWith
import ca.allanwang.kau.utils.color
import ca.allanwang.kau.utils.fadeScaleTransition
import ca.allanwang.kau.utils.navigationBarColor
import ca.allanwang.kau.utils.postDelayed
import ca.allanwang.kau.utils.scaleXY
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.statusBarColor
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.intro.*
import com.pitchedapps.frost.intro.BaseIntroFragment
import com.pitchedapps.frost.intro.IntroAccountFragment
import com.pitchedapps.frost.intro.IntroFragmentEnd
import com.pitchedapps.frost.intro.IntroFragmentTheme
import com.pitchedapps.frost.intro.IntroFragmentWelcome
import com.pitchedapps.frost.intro.IntroTabContextFragment
import com.pitchedapps.frost.intro.IntroTabTouchFragment
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.cookies
import com.pitchedapps.frost.utils.launchNewTask
import org.jetbrains.anko.find
/**
* Created by Allan Wang on 2017-07-25.
*
@ -43,12 +72,12 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
private var barHasNext = true
val fragments = listOf(
IntroFragmentWelcome(),
IntroFragmentTheme(),
IntroAccountFragment(),
IntroTabTouchFragment(),
IntroTabContextFragment(),
IntroFragmentEnd()
IntroFragmentWelcome(),
IntroFragmentTheme(),
IntroAccountFragment(),
IntroTabTouchFragment(),
IntroTabContextFragment(),
IntroFragmentEnd()
)
override fun onCreate(savedInstanceState: Bundle?) {
@ -97,7 +126,6 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
page.alpha = 1f
page.translationX = 0f
}
}
fun finish(x: Float, y: Float) {
@ -107,9 +135,11 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
postDelayed(1000) { finish() }
}
val lastView: View? = fragments.last().view
arrayOf<View?>(skip, indicator, next,
lastView?.find(R.id.intro_title),
lastView?.find(R.id.intro_desc)).forEach {
arrayOf<View?>(
skip, indicator, next,
lastView?.find(R.id.intro_title),
lastView?.find(R.id.intro_desc)
).forEach {
it?.animate()?.alpha(0f)?.setDuration(600)?.start()
}
if (Prefs.textColor != Color.WHITE) {
@ -147,7 +177,6 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
}
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
@ -162,16 +191,19 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
if (barHasNext == hasNext) return
barHasNext = hasNext
next.fadeScaleTransition {
setIcon(if (barHasNext) GoogleMaterial.Icon.gmd_navigate_next else GoogleMaterial.Icon.gmd_done, color = Prefs.textColor)
setIcon(
if (barHasNext) GoogleMaterial.Icon.gmd_navigate_next else GoogleMaterial.Icon.gmd_done,
color = Prefs.textColor
)
}
skip.animate().scaleXY(if (barHasNext) 1f else 0f)
}
class IntroPageAdapter(fm: FragmentManager, private val fragments: List<BaseIntroFragment>) : FragmentPagerAdapter(fm) {
class IntroPageAdapter(fm: FragmentManager, private val fragments: List<BaseIntroFragment>) :
FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment = fragments[position]
override fun getCount(): Int = fragments.size
}
}
}

View File

@ -1,12 +1,28 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.Toolbar
import android.widget.ImageView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
@ -24,14 +40,18 @@ import com.pitchedapps.frost.facebook.profilePictureUrl
import com.pitchedapps.frost.glide.FrostGlide
import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.glide.transform
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Showcase
import com.pitchedapps.frost.utils.frostEvent
import com.pitchedapps.frost.utils.launchNewTask
import com.pitchedapps.frost.utils.logFrostEvent
import com.pitchedapps.frost.utils.setFrostColors
import com.pitchedapps.frost.web.LoginWebView
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.subjects.SingleSubject
/**
* Created by Allan Wang on 2017-06-01.
*/
@ -78,51 +98,62 @@ class LoginActivity : BaseActivity() {
private fun loadInfo(cookie: CookieModel) {
refresh = true
Single.zip<Boolean, String, Pair<Boolean, String>>(
profileSubject,
usernameSubject,
BiFunction(::Pair))
.observeOn(AndroidSchedulers.mainThread()).subscribe { (foundImage, name) ->
refresh = false
if (!foundImage) {
L.e { "Could not get profile photo; Invalid userId?" }
L._i { cookie }
}
textview.text = String.format(getString(R.string.welcome), name)
textview.fadeIn()
frostEvent("Login", "success" to true)
/*
* The user may have logged into an account that is already in the database
* We will let the db handle duplicates and load it now after the new account has been saved
*/
loadFbCookiesAsync {
val cookies = ArrayList(it)
Handler().postDelayed({
if (Showcase.intro)
launchNewTask<IntroActivity>(cookies, true)
else
launchNewTask<MainActivity>(cookies, true)
}, 1000)
}
}.disposeOnDestroy()
profileSubject,
usernameSubject,
BiFunction(::Pair)
)
.observeOn(AndroidSchedulers.mainThread()).subscribe { (foundImage, name) ->
refresh = false
if (!foundImage) {
L.e { "Could not get profile photo; Invalid userId?" }
L._i { cookie }
}
textview.text = String.format(getString(R.string.welcome), name)
textview.fadeIn()
frostEvent("Login", "success" to true)
/*
* The user may have logged into an account that is already in the database
* We will let the db handle duplicates and load it now after the new account has been saved
*/
loadFbCookiesAsync {
val cookies = ArrayList(it)
Handler().postDelayed({
if (Showcase.intro)
launchNewTask<IntroActivity>(cookies, true)
else
launchNewTask<MainActivity>(cookies, true)
}, 1000)
}
}.disposeOnDestroy()
loadProfile(cookie.id)
loadUsername(cookie)
}
private fun loadProfile(id: Long) {
profileLoader.load(profilePictureUrl(id))
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
profileSubject.onSuccess(true)
return false
}
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
profileSubject.onSuccess(true)
return false
}
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
e.logFrostEvent("Profile loading exception")
profileSubject.onSuccess(false)
return false
}
}).into(profile)
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
e.logFrostEvent("Profile loading exception")
profileSubject.onSuccess(false)
return false
}
}).into(profile)
}
private fun loadUsername(cookie: CookieModel) {
@ -146,5 +177,4 @@ class LoginActivity : BaseActivity() {
web.pauseTimers()
super.onPause()
}
}
}

View File

@ -1,8 +1,24 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.os.Bundle
import com.google.android.material.tabs.TabLayout
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.views.BadgedIcon
import io.reactivex.android.schedulers.AndroidSchedulers
@ -36,19 +52,19 @@ class MainActivity : BaseMainActivity() {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
val delta = positionOffset * (255 - 128).toFloat()
tabsForEachView { tabPosition, view ->
view.setAllAlpha(when (tabPosition) {
position -> 255.0f - delta
position + 1 -> 128.0f + delta
else -> 128f
})
view.setAllAlpha(
when (tabPosition) {
position -> 255.0f - delta
position + 1 -> 128.0f + delta
else -> 128f
}
)
}
}
})
viewPager.post { fragmentSubject.onNext(0); lastPosition = 0 } //trigger hook so title is set
}
private fun setupTabs() {
viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))
tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) {
@ -63,31 +79,31 @@ class MainActivity : BaseMainActivity() {
}
})
headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS)
.subscribeOn(Schedulers.newThread())
.map { Jsoup.parse(it) }
.filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist
.map {
val feed = it.select("[data-sigil*=feed] [data-sigil=count]")
val requests = it.select("[data-sigil*=requests] [data-sigil=count]")
val messages = it.select("[data-sigil*=messages] [data-sigil=count]")
val notifications = it.select("[data-sigil*=notifications] [data-sigil=count]")
return@map arrayOf(feed, requests, messages, notifications).map { e -> e?.getOrNull(0)?.ownText() }
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (feed, requests, messages, notifications) ->
tabsForEachView { _, view ->
when (view.iicon) {
FbItem.FEED.icon -> view.badgeText = feed
FbItem.FRIENDS.icon -> view.badgeText = requests
FbItem.MESSAGES.icon -> view.badgeText = messages
FbItem.NOTIFICATIONS.icon -> view.badgeText = notifications
}
.subscribeOn(Schedulers.newThread())
.map { Jsoup.parse(it) }
.filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist
.map {
val feed = it.select("[data-sigil*=feed] [data-sigil=count]")
val requests = it.select("[data-sigil*=requests] [data-sigil=count]")
val messages = it.select("[data-sigil*=messages] [data-sigil=count]")
val notifications = it.select("[data-sigil*=notifications] [data-sigil=count]")
return@map arrayOf(feed, requests, messages, notifications).map { e -> e?.getOrNull(0)?.ownText() }
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (feed, requests, messages, notifications) ->
tabsForEachView { _, view ->
when (view.iicon) {
FbItem.FEED.icon -> view.badgeText = feed
FbItem.FRIENDS.icon -> view.badgeText = requests
FbItem.MESSAGES.icon -> view.badgeText = messages
FbItem.NOTIFICATIONS.icon -> view.badgeText = notifications
}
}.disposeOnDestroy()
}
}.disposeOnDestroy()
adapter.pages.forEach {
tabs.addTab(tabs.newTab()
.setCustomView(BadgedIcon(this).apply { iicon = it.icon }))
.setCustomView(BadgedIcon(this).apply { iicon = it.icon })
)
}
}
}

View File

@ -1,11 +1,27 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.os.Bundle
import androidx.constraintlayout.widget.ConstraintLayout
import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import ca.allanwang.kau.utils.bindView
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
@ -47,4 +63,4 @@ class SelectorActivity : BaseActivity() {
background(container)
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.annotation.SuppressLint
@ -12,14 +28,33 @@ import ca.allanwang.kau.kpref.activity.CoreAttributeContract
import ca.allanwang.kau.kpref.activity.KPrefActivity
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.ui.views.RippleCanvas
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.finishSlideOut
import ca.allanwang.kau.utils.setMenuIcons
import ca.allanwang.kau.utils.startActivityForResult
import ca.allanwang.kau.utils.startLink
import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.withSceneTransitionAnimation
import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.enums.Support
import com.pitchedapps.frost.settings.*
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.settings.getAppearancePrefs
import com.pitchedapps.frost.settings.getBehaviourPrefs
import com.pitchedapps.frost.settings.getDebugPrefs
import com.pitchedapps.frost.settings.getExperimentalPrefs
import com.pitchedapps.frost.settings.getFeedPrefs
import com.pitchedapps.frost.settings.getNotificationPrefs
import com.pitchedapps.frost.settings.sendDebug
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.REQUEST_RESTART
import com.pitchedapps.frost.utils.cookies
import com.pitchedapps.frost.utils.frostChangelog
import com.pitchedapps.frost.utils.frostNavigationBar
import com.pitchedapps.frost.utils.launchNewTask
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostTheme
/**
* Created by Allan Wang on 2017-06-06.
@ -146,7 +181,6 @@ class SettingsActivity : KPrefActivity() {
iicon = CommunityMaterial.Icon.cmd_android_debug_bridge
visible = { Prefs.debugSettings }
}
}
fun shouldRestartMain() {
@ -179,9 +213,11 @@ class SettingsActivity : KPrefActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_settings, menu)
toolbar.tint(Prefs.iconColor)
setMenuIcons(menu, Prefs.iconColor,
R.id.action_email to GoogleMaterial.Icon.gmd_email,
R.id.action_changelog to GoogleMaterial.Icon.gmd_info)
setMenuIcons(
menu, Prefs.iconColor,
R.id.action_email to GoogleMaterial.Icon.gmd_email,
R.id.action_changelog to GoogleMaterial.Icon.gmd_info
)
return true
}
@ -201,4 +237,4 @@ class SettingsActivity : KPrefActivity() {
fun setFrostResult(flag: Int) {
resultFlag = resultFlag or flag
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.app.Activity
@ -5,21 +21,18 @@ import android.content.res.ColorStateList
import android.os.Bundle
import android.view.View
import android.view.animation.AnimationUtils
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.kotlin.lazyContext
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.scaleXY
import ca.allanwang.kau.utils.setIcon
import com.pitchedapps.frost.R
import ca.allanwang.kau.utils.withAlpha
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
import com.mikepenz.fastadapter_extensions.drag.ItemTouchCallback
import com.mikepenz.fastadapter_extensions.drag.SimpleDragCallback
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.dbflow.TAB_COUNT
import com.pitchedapps.frost.dbflow.loadFbTabs
import com.pitchedapps.frost.dbflow.save
@ -28,7 +41,7 @@ import com.pitchedapps.frost.iitems.TabIItem
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.setFrostColors
import kotlinx.android.synthetic.main.activity_tab_customizer.*
import java.util.*
import java.util.Collections
/**
* Created by Allan Wang on 26/11/17.
@ -96,9 +109,9 @@ class TabCustomizerActivity : BaseActivity() {
override fun itemTouchDropped(oldPosition: Int, newPosition: Int) = Unit
}
private class TabDragCallback(
directions: Int, itemTouchCallback: ItemTouchCallback
directions: Int,
itemTouchCallback: ItemTouchCallback
) : SimpleDragCallback(directions, itemTouchCallback) {
private var draggingView: TabIItem.ViewHolder? = null
@ -122,7 +135,5 @@ class TabCustomizerActivity : BaseActivity() {
}
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.activities
import android.annotation.SuppressLint
@ -5,25 +21,52 @@ import android.content.Intent
import android.graphics.PointF
import android.net.Uri
import android.os.Bundle
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.appcompat.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.widget.FrameLayout
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import ca.allanwang.kau.swipe.kauSwipeOnCreate
import ca.allanwang.kau.swipe.kauSwipeOnDestroy
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.copyToClipboard
import ca.allanwang.kau.utils.darken
import ca.allanwang.kau.utils.dpToPx
import ca.allanwang.kau.utils.finishSlideOut
import ca.allanwang.kau.utils.navigationBarColor
import ca.allanwang.kau.utils.setMenuIcons
import ca.allanwang.kau.utils.shareText
import ca.allanwang.kau.utils.statusBarColor
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.toDrawable
import ca.allanwang.kau.utils.toast
import ca.allanwang.kau.utils.withAlpha
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.contracts.*
import com.pitchedapps.frost.contracts.ActivityContract
import com.pitchedapps.frost.contracts.FileChooserContract
import com.pitchedapps.frost.contracts.FileChooserDelegate
import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.VideoViewHolder
import com.pitchedapps.frost.enums.OverlayContext
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.services.FrostRunnable
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.ARG_URL
import com.pitchedapps.frost.utils.ARG_USER_ID
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.Showcase
import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostColors
import com.pitchedapps.frost.views.FrostContentWeb
import com.pitchedapps.frost.views.FrostVideoViewer
import com.pitchedapps.frost.views.FrostWebView
@ -31,7 +74,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import okhttp3.HttpUrl
/**
* Created by Allan Wang on 2017-06-01.
*
@ -103,8 +145,8 @@ class WebOverlayActivity : WebOverlayActivityBase(false)
@SuppressLint("Registered")
open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseActivity(),
ActivityContract, FrostContentContainer,
VideoViewHolder, FileChooserContract by FileChooserDelegate() {
ActivityContract, FrostContentContainer,
VideoViewHolder, FileChooserContract by FileChooserDelegate() {
override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper)
val toolbar: Toolbar by bindView(R.id.overlay_toolbar)
@ -156,9 +198,9 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
content.bind(this)
content.titleObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe { toolbar.title = it }
.disposeOnDestroy()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { toolbar.title = it }
.disposeOnDestroy()
with(web) {
if (forceBasicAgent) //todo check; the webview already adds it dynamically
@ -235,7 +277,10 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
kauSwipeOnDestroy()
}
override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams) {
override fun openFileChooser(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
) {
openMediaPicker(filePathCallback, fileChooserParams)
}
@ -247,9 +292,11 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
menuInflater.inflate(R.menu.menu_web, menu)
overlayContext?.onMenuCreate(this, menu)
toolbar.tint(Prefs.iconColor)
setMenuIcons(menu, Prefs.iconColor,
R.id.action_share to CommunityMaterial.Icon2.cmd_share,
R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy)
setMenuIcons(
menu, Prefs.iconColor,
R.id.action_share to CommunityMaterial.Icon2.cmd_share,
R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy
)
return true
}
@ -270,5 +317,4 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
*/
override var videoViewer: FrostVideoViewer? = null
override val lowerVideoPadding: PointF = PointF(0f, 0f)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
import com.mikepenz.iconics.typeface.IIcon
@ -25,4 +41,4 @@ interface MainActivityContract : ActivityContract, MainFabContract {
interface MainFabContract {
fun showFab(iicon: IIcon, clickEvent: () -> Unit)
fun hideFab()
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
/**
@ -21,10 +37,8 @@ interface DynamicUiContract {
*/
fun reloadTextSize()
/**
* Change text size without propagation
*/
fun reloadTextSizeSelf()
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
import android.app.Activity
@ -17,12 +33,19 @@ import com.pitchedapps.frost.utils.L
const val MEDIA_CHOOSER_RESULT = 67
interface FileChooserActivityContract {
fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams)
fun openFileChooser(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
)
}
interface FileChooserContract {
var filePathCallback: ValueCallback<Array<Uri>?>?
fun Activity.openMediaPicker(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams)
fun Activity.openMediaPicker(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
)
fun Activity.onActivityResultWeb(requestCode: Int, resultCode: Int, intent: Intent?): Boolean
}
@ -30,7 +53,10 @@ class FileChooserDelegate : FileChooserContract {
override var filePathCallback: ValueCallback<Array<Uri>?>? = null
override fun Activity.openMediaPicker(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams) {
override fun Activity.openMediaPicker(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
) {
kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ ->
if (!granted) {
filePathCallback.onReceiveValue(null)
@ -52,5 +78,4 @@ class FileChooserDelegate : FileChooserContract {
filePathCallback = null
return true
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
import android.view.View
@ -23,7 +39,6 @@ interface FrostContentContainer {
* Update toolbar title
*/
fun setTitle(title: String)
}
/**
@ -84,7 +99,6 @@ interface FrostContentParent : DynamicUiContract {
* For those cases, we will return false to stop it
*/
fun registerTransition(urlChanged: Boolean, animate: Boolean): Boolean
}
/**
@ -147,5 +161,4 @@ interface FrostContentCore : DynamicUiContract {
* Signal destruction to release some content manually
*/
fun destroy()
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
import io.reactivex.subjects.BehaviorSubject
@ -27,5 +43,4 @@ interface FrostObservables {
other.progressObservable = progressObservable
other.titleObservable = titleObservable
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
import android.view.View
@ -18,12 +34,11 @@ interface FrostThemable {
fun reloadTheme()
fun setTextColors(color: Int, vararg textViews: TextView?) =
themeViews(color, *textViews) { setTextColor(it) }
themeViews(color, *textViews) { setTextColor(it) }
fun setBackgrounds(color: Int, vararg views: View?) =
themeViews(color, *views) { setBackgroundColor(it) }
themeViews(color, *views) { setBackgroundColor(it) }
fun <T : View> themeViews(color: Int, vararg views: T?, action: T.(Int) -> Unit) =
views.filterNotNull().forEach { it.action(color) }
}
views.filterNotNull().forEach { it.action(color) }
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.contracts
import android.app.Activity
@ -49,5 +65,4 @@ interface FrameWrapper {
setContentView(R.layout.activity_frame_wrapper)
frameWrapper.inflate(layoutRes, true)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.dbflow
import android.os.Parcel
@ -11,7 +27,13 @@ import com.raizlabs.android.dbflow.annotation.ConflictAction
import com.raizlabs.android.dbflow.annotation.Database
import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table
import com.raizlabs.android.dbflow.kotlinextensions.*
import com.raizlabs.android.dbflow.kotlinextensions.async
import com.raizlabs.android.dbflow.kotlinextensions.delete
import com.raizlabs.android.dbflow.kotlinextensions.eq
import com.raizlabs.android.dbflow.kotlinextensions.from
import com.raizlabs.android.dbflow.kotlinextensions.save
import com.raizlabs.android.dbflow.kotlinextensions.select
import com.raizlabs.android.dbflow.kotlinextensions.where
import com.raizlabs.android.dbflow.structure.BaseModel
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
@ -30,7 +52,8 @@ object CookiesDb {
@PaperParcel
@Table(database = CookiesDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, var cookie: String? = null) : BaseModel(), Parcelable {
data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, var cookie: String? = null) :
BaseModel(), Parcelable {
companion object {
@JvmField
val CREATOR = PaperParcelCookieModel.CREATOR
@ -40,18 +63,22 @@ data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null,
override fun writeToParcel(dest: Parcel, flags: Int) = PaperParcelCookieModel.writeToParcel(this, dest, flags)
}
fun loadFbCookie(id: Long): CookieModel? = (select from CookieModel::class where (CookieModel_Table.id eq id)).querySingle()
fun loadFbCookie(name: String): CookieModel? = (select from CookieModel::class where (CookieModel_Table.name eq name)).querySingle()
fun loadFbCookie(id: Long): CookieModel? =
(select from CookieModel::class where (CookieModel_Table.id eq id)).querySingle()
fun loadFbCookie(name: String): CookieModel? =
(select from CookieModel::class where (CookieModel_Table.name eq name)).querySingle()
/**
* Loads cookies sorted by name
*/
fun loadFbCookiesAsync(callback: (cookies: List<CookieModel>) -> Unit) {
(select from CookieModel::class).orderBy(CookieModel_Table.name, true).async().queryListResultCallback { _, tResult -> callback(tResult) }.execute()
(select from CookieModel::class).orderBy(CookieModel_Table.name, true).async()
.queryListResultCallback { _, tResult -> callback(tResult) }.execute()
}
fun loadFbCookiesSync(): List<CookieModel> = (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList()
fun loadFbCookiesSync(): List<CookieModel> =
(select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList()
inline fun saveFbCookie(cookie: CookieModel, crossinline callback: (() -> Unit) = {}) {
cookie.async save {
@ -69,24 +96,24 @@ fun removeCookie(id: Long) {
}
inline fun CookieModel.fetchUsername(crossinline callback: (String) -> Unit): Disposable =
ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { yes, _ ->
if (!yes) return@subscribe callback("")
var result = ""
try {
result = frostJsoup(cookie, FbItem.PROFILE.url).title()
L.d { "Fetch username found" }
} catch (e: Exception) {
if (e !is UnknownHostException)
e.logFrostEvent("Fetch username failed")
} finally {
if (result.isBlank() && (name?.isNotBlank() == true)) {
callback(name!!)
return@subscribe
}
if (name != result) {
name = result
saveFbCookie(this@fetchUsername)
}
callback(result)
ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { yes, _ ->
if (!yes) return@subscribe callback("")
var result = ""
try {
result = frostJsoup(cookie, FbItem.PROFILE.url).title()
L.d { "Fetch username found" }
} catch (e: Exception) {
if (e !is UnknownHostException)
e.logFrostEvent("Fetch username failed")
} finally {
if (result.isBlank() && (name?.isNotBlank() == true)) {
callback(name!!)
return@subscribe
}
if (name != result) {
name = result
saveFbCookie(this@fetchUsername)
}
callback(result)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.dbflow
import android.content.Context
@ -14,11 +30,10 @@ object DbUtils {
fun db(name: String) = FlowManager.getDatabase(name)
fun dbName(name: String) = "$name.db"
fun deleteDatabase(c: Context, name: String) = c.deleteDatabase(dbName(name))
}
inline fun <reified T : Any> List<T>.replace(dbName: String) {
L.d { "Replacing $dbName.db" }
DbUtils.db(dbName).reset()
FastStoreModelTransaction.saveBuilder(FlowManager.getModelAdapter(T::class.java)).addAll(this).build()
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.dbflow
import com.pitchedapps.frost.facebook.FbItem
@ -39,4 +55,4 @@ fun loadFbTabs(): List<FbItem> {
fun List<FbItem>.save() {
database<FbTabsDb>().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute()
}
}

View File

@ -1,8 +1,33 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.dbflow
import com.pitchedapps.frost.utils.L
import com.raizlabs.android.dbflow.annotation.*
import com.raizlabs.android.dbflow.kotlinextensions.*
import com.raizlabs.android.dbflow.annotation.ConflictAction
import com.raizlabs.android.dbflow.annotation.Database
import com.raizlabs.android.dbflow.annotation.Migration
import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table
import com.raizlabs.android.dbflow.kotlinextensions.async
import com.raizlabs.android.dbflow.kotlinextensions.eq
import com.raizlabs.android.dbflow.kotlinextensions.from
import com.raizlabs.android.dbflow.kotlinextensions.save
import com.raizlabs.android.dbflow.kotlinextensions.select
import com.raizlabs.android.dbflow.kotlinextensions.where
import com.raizlabs.android.dbflow.sql.SQLiteType
import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration
import com.raizlabs.android.dbflow.structure.BaseModel
@ -18,7 +43,8 @@ object NotificationDb {
}
@Migration(version = 2, database = NotificationDb::class)
class NotificationMigration2(modelClass: Class<NotificationModel>) : AlterTableMigration<NotificationModel>(modelClass) {
class NotificationMigration2(modelClass: Class<NotificationModel>) :
AlterTableMigration<NotificationModel>(modelClass) {
override fun onPreMigrate() {
super.onPreMigrate()
addColumn(SQLiteType.INTEGER, "epochIm")
@ -27,11 +53,14 @@ class NotificationMigration2(modelClass: Class<NotificationModel>) : AlterTableM
}
@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
data class NotificationModel(@PrimaryKey var id: Long = -1L,
var epoch: Long = -1L,
var epochIm: Long = -1L) : BaseModel()
data class NotificationModel(
@PrimaryKey var id: Long = -1L,
var epoch: Long = -1L,
var epochIm: Long = -1L
) : BaseModel()
fun lastNotificationTime(id: Long): NotificationModel = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
fun lastNotificationTime(id: Long): NotificationModel =
(select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
?: NotificationModel(id = id)
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
@ -40,4 +69,4 @@ fun saveNotificationTime(notificationModel: NotificationModel, callback: (() ->
L._d { notificationModel }
callback?.invoke()
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.debugger
import ca.allanwang.kau.logging.KauLoggerExtension
@ -32,21 +48,23 @@ import java.util.zip.ZipOutputStream
*
* Inspired by <a href="https://github.com/JonasCz/save-for-offline">Save for Offline</a>
*/
class OfflineWebsite(private val url: String,
private val cookie: String = "",
baseUrl: String? = null,
private val html: String? = null,
/**
* Directory that holds all the files
*/
val baseDir: File,
private val userAgent: String = USER_AGENT_BASIC) {
class OfflineWebsite(
private val url: String,
private val cookie: String = "",
baseUrl: String? = null,
private val html: String? = null,
/**
* Directory that holds all the files
*/
val baseDir: File,
private val userAgent: String = USER_AGENT_BASIC
) {
/**
* Supplied url without the queries
*/
private val baseUrl = (baseUrl ?: url.substringBefore("?")
.substringBefore(".com")).trim('/')
.substringBefore(".com")).trim('/')
private val mainFile = File(baseDir, "index.html")
private val assetDir = File(baseDir, "assets")
@ -67,11 +85,11 @@ class OfflineWebsite(private val url: String,
private val cssQueue = mutableSetOf<String>()
private fun request(url: String) = Request.Builder()
.header("Cookie", cookie)
.header("User-Agent", userAgent)
.url(url)
.get()
.call()
.header("Cookie", cookie)
.header("User-Agent", userAgent)
.url(url)
.get()
.call()
private val compositeDisposable = CompositeDisposable()
@ -94,7 +112,6 @@ class OfflineWebsite(private val url: String,
return callback(false)
}
if (!assetDir.createFreshDir()) {
L.e { "Could not create ${assetDir.absolutePath}" }
return callback(false)
@ -245,8 +262,10 @@ class OfflineWebsite(private val url: String,
}
})
private inline fun <T> String.downloadUrl(fallback: () -> T,
action: (file: File, body: ResponseBody) -> T): T {
private inline fun <T> String.downloadUrl(
fallback: () -> T,
action: (file: File, body: ResponseBody) -> T
): T {
val file = File(assetDir, fileName())
if (!file.createNewFile()) {
@ -289,11 +308,10 @@ class OfflineWebsite(private val url: String,
if (mapped != null) return mapped
val candidate = substringBefore("?").trim('/')
.substringAfterLast("/").shorten()
.substringAfterLast("/").shorten()
val index = atomicInt.getAndIncrement()
var newUrl = "a${index}_$candidate"
/**
@ -308,10 +326,10 @@ class OfflineWebsite(private val url: String,
}
private fun String.shorten() =
if (length <= 10) this else substring(length - 10)
if (length <= 10) this else substring(length - 10)
private fun Set<String>.clean(): List<String> =
filter(String::isNotBlank).filter { it.startsWith("http") }
filter(String::isNotBlank).filter { it.startsWith("http") }
private fun reset() {
cancelled = false
@ -326,5 +344,4 @@ class OfflineWebsite(private val url: String,
compositeDisposable.dispose()
L.v { "Request cancelled" }
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.enums
import androidx.annotation.StringRes
@ -16,4 +32,4 @@ enum class FeedSort(@StringRes val textRes: Int, val item: FbItem) {
val values = values() //save one instance
operator fun invoke(index: Int) = values[index]
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.enums
import com.pitchedapps.frost.R
@ -7,23 +23,24 @@ import com.pitchedapps.frost.utils.Prefs
* Created by Allan Wang on 2017-08-19.
*/
enum class MainActivityLayout(
val titleRes: Int,
val layoutRes: Int,
val backgroundColor: () -> Int,
val iconColor: () -> Int) {
val titleRes: Int,
val layoutRes: Int,
val backgroundColor: () -> Int,
val iconColor: () -> Int
) {
TOP_BAR(R.string.top_bar,
R.layout.activity_main,
{ Prefs.headerColor },
{ Prefs.iconColor }),
R.layout.activity_main,
{ Prefs.headerColor },
{ Prefs.iconColor }),
BOTTOM_BAR(R.string.bottom_bar,
R.layout.activity_main_bottom_tabs,
{ Prefs.bgColor },
{ Prefs.textColor });
R.layout.activity_main_bottom_tabs,
{ Prefs.bgColor },
{ Prefs.textColor });
companion object {
val values = values() //save one instance
operator fun invoke(index: Int) = values[index]
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.enums
import android.content.Context
@ -52,12 +68,13 @@ enum class OverlayContext(private val menuItem: FrostMenuItem?) : EnumBundle<Ove
* Frame for an injectable menu item
*/
class FrostMenuItem(
val id: Int,
val fbItem: FbItem,
val showAsAction: Int = MenuItem.SHOW_AS_ACTION_IF_ROOM) {
val id: Int,
val fbItem: FbItem,
val showAsAction: Int = MenuItem.SHOW_AS_ACTION_IF_ROOM
) {
fun addToMenu(context: Context, menu: Menu, index: Int) {
val item = menu.add(Menu.NONE, id, index, fbItem.titleId)
item.icon = fbItem.icon.toDrawable(context, 18)
item.setShowAsAction(showAsAction)
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.enums
import android.content.Context
@ -21,4 +37,4 @@ enum class Support(@StringRes val title: Int) {
}
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.enums
import android.graphics.Color
@ -14,61 +30,63 @@ import com.pitchedapps.frost.utils.Prefs
const val FACEBOOK_BLUE = 0xff3b5998.toInt()
const val BLUE_LIGHT = 0xff5d86dd.toInt()
enum class Theme(@StringRes val textRes: Int,
val injector: InjectorContract,
private val textColorGetter: () -> Int,
private val accentColorGetter: () -> Int,
private val backgroundColorGetter: () -> Int,
private val headerColorGetter: () -> Int,
private val iconColorGetter: () -> Int) {
enum class Theme(
@StringRes val textRes: Int,
val injector: InjectorContract,
private val textColorGetter: () -> Int,
private val accentColorGetter: () -> Int,
private val backgroundColorGetter: () -> Int,
private val headerColorGetter: () -> Int,
private val iconColorGetter: () -> Int
) {
DEFAULT(R.string.kau_default,
JsActions.EMPTY,
{ 0xde000000.toInt() },
{ FACEBOOK_BLUE },
{ 0xfffafafa.toInt() },
{ FACEBOOK_BLUE },
{ Color.WHITE }),
JsActions.EMPTY,
{ 0xde000000.toInt() },
{ FACEBOOK_BLUE },
{ 0xfffafafa.toInt() },
{ FACEBOOK_BLUE },
{ Color.WHITE }),
LIGHT(R.string.kau_light,
CssAssets.MATERIAL_LIGHT,
{ 0xde000000.toInt() },
{ FACEBOOK_BLUE },
{ 0xfffafafa.toInt() },
{ FACEBOOK_BLUE },
{ Color.WHITE }),
CssAssets.MATERIAL_LIGHT,
{ 0xde000000.toInt() },
{ FACEBOOK_BLUE },
{ 0xfffafafa.toInt() },
{ FACEBOOK_BLUE },
{ Color.WHITE }),
DARK(R.string.kau_dark,
CssAssets.MATERIAL_DARK,
{ Color.WHITE },
{ BLUE_LIGHT },
{ 0xff303030.toInt() },
{ 0xff2e4b86.toInt() },
{ Color.WHITE }),
CssAssets.MATERIAL_DARK,
{ Color.WHITE },
{ BLUE_LIGHT },
{ 0xff303030.toInt() },
{ 0xff2e4b86.toInt() },
{ Color.WHITE }),
AMOLED(R.string.kau_amoled,
CssAssets.MATERIAL_AMOLED,
{ Color.WHITE },
{ BLUE_LIGHT },
{ Color.BLACK },
{ Color.BLACK },
{ Color.WHITE }),
CssAssets.MATERIAL_AMOLED,
{ Color.WHITE },
{ BLUE_LIGHT },
{ Color.BLACK },
{ Color.BLACK },
{ Color.WHITE }),
GLASS(R.string.kau_glass,
CssAssets.MATERIAL_GLASS,
{ Color.WHITE },
{ BLUE_LIGHT },
{ 0x80000000.toInt() },
{ 0xb3000000.toInt() },
{ Color.WHITE }),
CssAssets.MATERIAL_GLASS,
{ Color.WHITE },
{ BLUE_LIGHT },
{ 0x80000000.toInt() },
{ 0xb3000000.toInt() },
{ Color.WHITE }),
CUSTOM(R.string.kau_custom,
CssAssets.CUSTOM,
{ Prefs.customTextColor },
{ Prefs.customAccentColor },
{ Prefs.customBackgroundColor },
{ Prefs.customHeaderColor },
{ Prefs.customIconColor });
CssAssets.CUSTOM,
{ Prefs.customTextColor },
{ Prefs.customAccentColor },
{ Prefs.customBackgroundColor },
{ Prefs.customHeaderColor },
{ Prefs.customIconColor });
val textColor: Int
get() = textColorGetter()
@ -89,4 +107,4 @@ enum class Theme(@StringRes val textRes: Int,
val values = values() //save one instance
operator fun invoke(index: Int) = values[index]
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook
/**
@ -12,9 +28,12 @@ fun profilePictureUrl(id: Long) = "https://graph.facebook.com/$id/picture?type=l
const val FB_LOGIN_URL = "${FB_URL_BASE}login"
const val FB_HOME_URL = "${FB_URL_BASE}home.php"
const val USER_AGENT_FULL = "Mozilla/5.0 (Linux; Android 4.4.2; en-us; SAMSUNG SM-G900T Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36"
const val USER_AGENT_BASIC_OLD = "Mozilla/5.0 (Linux; Android 6.0) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.4633 Mobile Safari/537.10+"
const val USER_AGENT_MESSENGER = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
const val USER_AGENT_FULL =
"Mozilla/5.0 (Linux; Android 4.4.2; en-us; SAMSUNG SM-G900T Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36"
const val USER_AGENT_BASIC_OLD =
"Mozilla/5.0 (Linux; Android 6.0) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.4633 Mobile Safari/537.10+"
const val USER_AGENT_MESSENGER =
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
const val USER_AGENT_BASIC = USER_AGENT_MESSENGER
/**
@ -27,4 +46,4 @@ const val WEB_LOAD_DELAY = 50L
* Note that transitions are also called from onFinish, so this value
* will never make a load slower than it is
*/
const val WEB_COMMIT_LOAD_DELAY = 200L
const val WEB_COMMIT_LOAD_DELAY = 200L

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook
import android.app.Activity
@ -38,13 +54,13 @@ object FbCookie {
val cookies = cookie.split(";").map { Pair(it, SingleSubject.create<Boolean>()) }
cookies.forEach { (cookie, callback) -> setCookie(FB_URL_BASE, cookie) { callback.onSuccess(it) } }
Observable.zip<Boolean, Unit>(cookies.map { (_, callback) -> callback.toObservable() }) {}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
callback?.invoke()
L.d { "Cookies set" }
L._d { cookie }
flush()
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
callback?.invoke()
L.d { "Cookies set" }
L._d { cookie }
flush()
}
}
}
}
@ -132,4 +148,4 @@ object FbCookie {
}
} else callback()
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook
import androidx.annotation.StringRes
@ -15,10 +31,10 @@ import com.pitchedapps.frost.utils.EnumBundleCompanion
import com.pitchedapps.frost.utils.EnumCompanion
enum class FbItem(
@StringRes val titleId: Int,
val icon: IIcon,
relativeUrl: String,
val fragmentCreator: () -> BaseFragment = ::WebFragment
@StringRes val titleId: Int,
val icon: IIcon,
relativeUrl: String,
val fragmentCreator: () -> BaseFragment = ::WebFragment
) : EnumBundle<FbItem> {
ACTIVITY_LOG(R.string.activity_log, GoogleMaterial.Icon.gmd_list, "me/allactivity"),

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook
/**
@ -29,4 +45,3 @@ val FB_IMAGE_ID_MATCHER: Regex = Regex("fbcdn.*?/[0-9]+_([0-9]+)_")
val FB_REDIRECT_URL_MATCHER: Regex = Regex("url=(.*?fbcdn.*?)\"")
operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex)

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook
import com.pitchedapps.frost.utils.L
@ -91,13 +107,13 @@ class FbUrlFormatter(url: String) {
* That shouldn't break anything
*/
val discardable = arrayOf(
"http://lm.facebook.com/l.php?u=",
"https://lm.facebook.com/l.php?u=",
"http://m.facebook.com/l.php?u=",
"https://m.facebook.com/l.php?u=",
"http://touch.facebook.com/l.php?u=",
"https://touch.facebook.com/l.php?u=",
VIDEO_REDIRECT
"http://lm.facebook.com/l.php?u=",
"https://lm.facebook.com/l.php?u=",
"http://m.facebook.com/l.php?u=",
"https://m.facebook.com/l.php?u=",
"http://touch.facebook.com/l.php?u=",
"https://touch.facebook.com/l.php?u=",
VIDEO_REDIRECT
)
/**
@ -108,13 +124,13 @@ class FbUrlFormatter(url: String) {
val discardableQueries = arrayOf("ref", "refid", "SharedWith")
val converter = listOf(
"\\3C " to "%3C", "\\3E " to "%3E", "\\23 " to "%23", "\\25 " to "%25",
"\\7B " to "%7B", "\\7D " to "%7D", "\\7C " to "%7C", "\\5C " to "%5C",
"\\5E " to "%5E", "\\7E " to "%7E", "\\5B " to "%5B", "\\5D " to "%5D",
"\\60 " to "%60", "\\3B " to "%3B", "\\2F " to "%2F", "\\3F " to "%3F",
"\\3A " to "%3A", "\\40 " to "%40", "\\3D " to "%3D", "\\26 " to "%26",
"\\24 " to "%24", "\\2B " to "%2B", "\\22 " to "%22", "\\2C " to "%2C",
"\\20 " to "%20"
"\\3C " to "%3C", "\\3E " to "%3E", "\\23 " to "%23", "\\25 " to "%25",
"\\7B " to "%7B", "\\7D " to "%7D", "\\7C " to "%7C", "\\5C " to "%5C",
"\\5E " to "%5E", "\\7E " to "%7E", "\\5B " to "%5B", "\\5D " to "%5D",
"\\60 " to "%60", "\\3B " to "%3B", "\\2F " to "%2F", "\\3F " to "%3F",
"\\3A " to "%3A", "\\40 " to "%40", "\\3D " to "%3D", "\\26 " to "%26",
"\\24 " to "%24", "\\2B " to "%2B", "\\22 " to "%22", "\\2C " to "%2C",
"\\20 " to "%20"
)
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.dbflow.CookieModel
@ -54,7 +70,6 @@ interface FrostParser<out T : Any> {
* Call parsing with given data
*/
fun parseFromData(cookie: String?, text: String): ParseResponse<T>?
}
const val FALLBACK_TIME_MOD = 1000000
@ -92,7 +107,7 @@ internal abstract class FrostParserBase<out T : Any>(private val redirectToText:
}
final override fun parseFromUrl(cookie: String?, url: String): ParseResponse<T>? =
parse(cookie, frostJsoup(cookie, url))
parse(cookie, frostJsoup(cookie, url))
override fun parse(cookie: String?, document: Document): ParseResponse<T>? {
cookie ?: return null
@ -109,17 +124,17 @@ internal abstract class FrostParserBase<out T : Any>(private val redirectToText:
* Returns the formatted url, or an empty string if nothing was found
*/
protected fun Element.getInnerImgStyle(): String? =
select("i.img[style*=url]").getStyleUrl()
select("i.img[style*=url]").getStyleUrl()
protected fun Elements.getStyleUrl(): String? =
FB_CSS_URL_MATCHER.find(attr("style"))[1]?.formattedFbUrl
FB_CSS_URL_MATCHER.find(attr("style"))[1]?.formattedFbUrl
protected open fun textToDoc(text: String): Document? =
if (!redirectToText) Jsoup.parse(text)
else throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc")
if (!redirectToText) Jsoup.parse(text)
else throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc")
protected fun parseLink(element: Element?): FrostLink? {
val a = element?.getElementsByTag("a")?.first() ?: return null
return FrostLink(a.text(), a.attr("href"))
}
}
}

View File

@ -1,7 +1,27 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER
import com.pitchedapps.frost.facebook.FB_MESSAGE_NOTIF_ID_MATCHER
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.services.NotificationContent
import com.pitchedapps.frost.utils.L
import org.apache.commons.text.StringEscapeUtils
@ -19,12 +39,12 @@ import org.jsoup.nodes.Element
object MessageParser : FrostParser<FrostMessages> by MessageParserImpl() {
fun queryUser(cookie: String?, name: String) = parseFromUrl(cookie, "${FbItem.MESSAGES.url}/?q=$name")
}
data class FrostMessages(val threads: List<FrostThread>,
val seeMore: FrostLink?,
val extraLinks: List<FrostLink>
data class FrostMessages(
val threads: List<FrostThread>,
val seeMore: FrostLink?,
val extraLinks: List<FrostLink>
) : ParseNotification {
override fun toString() = StringBuilder().apply {
append("FrostMessages {\n")
@ -35,19 +55,19 @@ data class FrostMessages(val threads: List<FrostThread>,
}.toString()
override fun getUnreadNotifications(data: CookieModel) =
threads.asSequence().filter(FrostThread::unread).map {
with(it) {
NotificationContent(
data = data,
id = id,
href = url,
title = title,
text = content ?: "",
timestamp = time,
profileUrl = img
)
}
}.toList()
threads.asSequence().filter(FrostThread::unread).map {
with(it) {
NotificationContent(
data = data,
id = id,
href = url,
title = title,
text = content ?: "",
timestamp = time,
profileUrl = img
)
}
}.toList()
}
/**
@ -58,14 +78,16 @@ data class FrostMessages(val threads: List<FrostThread>,
* [unread] true if image is unread, false otherwise
* [content] optional string for thread
*/
data class FrostThread(val id: Long,
val img: String?,
val title: String,
val time: Long,
val url: String,
val unread: Boolean,
val content: String?,
val contentImgUrl: String?)
data class FrostThread(
val id: Long,
val img: String?,
val title: String,
val time: Long,
val url: String,
val unread: Boolean,
val content: String?,
val contentImgUrl: String?
)
private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
@ -92,11 +114,12 @@ private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
override fun parseImpl(doc: Document): FrostMessages? {
val threadList = doc.getElementById("threadlist_rows") ?: return null
val threads: List<FrostThread> = threadList.getElementsByAttributeValueMatching("id", ".*${FB_MESSAGE_NOTIF_ID_MATCHER.pattern}.*")
val threads: List<FrostThread> =
threadList.getElementsByAttributeValueMatching("id", ".*${FB_MESSAGE_NOTIF_ID_MATCHER.pattern}.*")
.mapNotNull(this::parseMessage)
val seeMore = parseLink(doc.getElementById("see_older_threads"))
val extraLinks = threadList.nextElementSibling().select("a")
.mapNotNull(this::parseLink)
.mapNotNull(this::parseLink)
return FrostMessages(threads, seeMore, extraLinks)
}
@ -106,21 +129,20 @@ private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
val epoch = FB_EPOCH_MATCHER.find(abbr.attr("data-store"))[1]?.toLongOrNull() ?: -1L
//fetch id
val id = FB_MESSAGE_NOTIF_ID_MATCHER.find(element.id())[1]?.toLongOrNull()
?: System.currentTimeMillis() % FALLBACK_TIME_MOD
?: System.currentTimeMillis() % FALLBACK_TIME_MOD
val snippet = element.select("span.snippet").firstOrNull()
val content = snippet?.text()?.trim()
val contentImg = snippet?.select("i[style*=url]")?.getStyleUrl()
val img = element.getInnerImgStyle()
return FrostThread(
id = id,
img = img,
title = a.text(),
time = epoch,
url = a.attr("href").formattedFbUrl,
unread = !element.hasClass("acw"),
content = content,
contentImgUrl = contentImg
id = id,
img = img,
title = a.text(),
time = epoch,
url = a.attr("href").formattedFbUrl,
unread = !element.hasClass("acw"),
content = content,
contentImgUrl = contentImg
)
}
}

View File

@ -1,7 +1,27 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER
import com.pitchedapps.frost.facebook.FB_NOTIF_ID_MATCHER
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.services.NotificationContent
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@ -13,8 +33,8 @@ import org.jsoup.nodes.Element
object NotifParser : FrostParser<FrostNotifs> by NotifParserImpl()
data class FrostNotifs(
val notifs: List<FrostNotif>,
val seeMore: FrostLink?
val notifs: List<FrostNotif>,
val seeMore: FrostLink?
) : ParseNotification {
override fun toString() = StringBuilder().apply {
append("FrostNotifs {\n")
@ -24,19 +44,19 @@ data class FrostNotifs(
}.toString()
override fun getUnreadNotifications(data: CookieModel) =
notifs.asSequence().filter(FrostNotif::unread).map {
with(it) {
NotificationContent(
data = data,
id = id,
href = url,
title = null,
text = content,
timestamp = time,
profileUrl = img
)
}
}.toList()
notifs.asSequence().filter(FrostNotif::unread).map {
with(it) {
NotificationContent(
data = data,
id = id,
href = url,
title = null,
text = content,
timestamp = time,
profileUrl = img
)
}
}.toList()
}
/**
@ -49,14 +69,16 @@ data class FrostNotifs(
* [timeString] text version of time from Facebook
* [thumbnailUrl] optional thumbnail url if existent
*/
data class FrostNotif(val id: Long,
val img: String?,
val time: Long,
val url: String,
val unread: Boolean,
val content: String,
val timeString: String,
val thumbnailUrl: String?)
data class FrostNotif(
val id: Long,
val img: String?,
val time: Long,
val url: String,
val unread: Boolean,
val content: String,
val timeString: String,
val thumbnailUrl: String?
)
private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
@ -67,8 +89,8 @@ private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
override fun parseImpl(doc: Document): FrostNotifs? {
val notificationList = doc.getElementById("notifications_list") ?: return null
val notifications = notificationList
.getElementsByAttributeValueMatching("id", ".*${FB_NOTIF_ID_MATCHER.pattern}.*")
.mapNotNull(this::parseNotif)
.getElementsByAttributeValueMatching("id", ".*${FB_NOTIF_ID_MATCHER.pattern}.*")
.mapNotNull(this::parseNotif)
val seeMore = parseLink(doc.getElementsByAttributeValue("href", "/notifications.php?more").first())
return FrostNotifs(notifications, seeMore)
}
@ -79,22 +101,20 @@ private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
val epoch = FB_EPOCH_MATCHER.find(abbr.attr("data-store"))[1]?.toLongOrNull() ?: -1L
//fetch id
val id = FB_NOTIF_ID_MATCHER.find(element.id())[1]?.toLongOrNull()
?: System.currentTimeMillis() % FALLBACK_TIME_MOD
?: System.currentTimeMillis() % FALLBACK_TIME_MOD
val img = element.getInnerImgStyle()
val timeString = abbr.text()
val content = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove &nbsp;
val thumbnail = element.selectFirst("img.thumbnail")?.attr("src")
return FrostNotif(
id = id,
img = img,
time = epoch,
url = a.attr("href").formattedFbUrl,
unread = !element.hasClass("acw"),
content = content,
timeString = timeString,
thumbnailUrl = if (thumbnail?.isNotEmpty() == true) thumbnail else null
id = id,
img = img,
time = epoch,
url = a.attr("href").formattedFbUrl,
unread = !element.hasClass("acw"),
content = content,
timeString = timeString,
thumbnailUrl = if (thumbnail?.isNotEmpty() == true) thumbnail else null
)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.parsers
import ca.allanwang.kau.searchview.SearchItem
@ -46,9 +62,9 @@ data class FrostSearch(val href: String, val title: String, val description: Str
companion object {
fun create(href: String, title: String, description: String?) = FrostSearch(
with(href.indexOf("?")) { if (this == -1) href else href.substring(0, this) },
title.format(),
description?.format()
with(href.indexOf("?")) { if (this == -1) href else href.substring(0, this) },
title.format(),
description?.format()
)
}
}
@ -61,17 +77,18 @@ private class SearchParserImpl : FrostParserBase<FrostSearches>(false) {
override fun parseImpl(doc: Document): FrostSearches? {
val container: Element = doc.getElementById("BrowseResultsContainer")
?: doc.getElementById("root")
?: return null
?: doc.getElementById("root")
?: return null
/**
*
* Removed [data-store*=result_id]
*/
return FrostSearches(container.select("a.touchable[href]").filter(Element::hasText).map {
FrostSearch.create(it.attr("href").formattedFbUrl,
it.select("._uoi").first()?.text() ?: "",
it.select("._1tcc").first()?.text())
FrostSearch.create(
it.attr("href").formattedFbUrl,
it.select("._uoi").first()?.text() ?: "",
it.select("._1tcc").first()?.text()
)
}.filter { it.title.isNotBlank() })
}
}
}

View File

@ -1,7 +1,29 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.FB_DTSG_MATCHER
import com.pitchedapps.frost.facebook.FB_JSON_URL_MATCHER
import com.pitchedapps.frost.facebook.FB_REV_MATCHER
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FB_USER_MATCHER
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.rx.RxFlyweight
import com.pitchedapps.frost.utils.L
import io.reactivex.Single
@ -21,10 +43,9 @@ private class RxAuth : RxFlyweight<String, Long, RequestAuth>() {
override fun call(input: String) = input.getAuth()
override fun validate(input: String, cond: Long) =
System.currentTimeMillis() - cond < 3600000 // valid for an hour
System.currentTimeMillis() - cond < 3600000 // valid for an hour
override fun cache(input: String) = System.currentTimeMillis()
}
private val auth = RxAuth()
@ -48,10 +69,12 @@ fun String?.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) {
/**
* Underlying container for all fb requests
*/
data class RequestAuth(val userId: Long = -1,
val cookie: String = "",
val fb_dtsg: String = "",
val rev: String = "") {
data class RequestAuth(
val userId: Long = -1,
val cookie: String = "",
val fb_dtsg: String = "",
val rev: String = ""
) {
val isComplete
get() = userId > 0 && cookie.isNotEmpty() && fb_dtsg.isNotEmpty() && rev.isNotEmpty()
}
@ -64,8 +87,8 @@ class FrostRequest<out T : Any?>(val call: Call, private val invoke: (Call) -> T
}
internal inline fun <T : Any?> RequestAuth.frostRequest(
noinline invoke: (Call) -> T,
builder: Request.Builder.() -> Request.Builder // to ensure we don't do anything extra at the end
noinline invoke: (Call) -> T,
builder: Request.Builder.() -> Request.Builder // to ensure we don't do anything extra at the end
): FrostRequest<T> {
val request = cookie.requestBuilder()
request.builder()
@ -75,8 +98,10 @@ internal inline fun <T : Any?> RequestAuth.frostRequest(
val httpClient: OkHttpClient by lazy {
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG)
builder.addInterceptor(HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BASIC))
builder.addInterceptor(
HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BASIC)
)
builder.build()
}
@ -97,7 +122,7 @@ internal fun List<Pair<String, Any?>>.withEmptyData(vararg key: String): List<Pa
internal fun String?.requestBuilder(): Request.Builder {
val builder = Request.Builder()
.header("User-Agent", USER_AGENT_BASIC)
.header("User-Agent", USER_AGENT_BASIC)
if (this != null)
builder.header("Cookie", this)
// .cacheControl(CacheControl.FORCE_NETWORK)
@ -112,9 +137,9 @@ fun String.getAuth(): RequestAuth {
val id = FB_USER_MATCHER.find(this)[1]?.toLong() ?: return auth
auth = auth.copy(userId = id)
val call = this.requestBuilder()
.url(FB_URL_BASE)
.get()
.call()
.url(FB_URL_BASE)
.get()
.call()
call.execute().body()?.charStream()?.useLines { lines ->
lines.forEach {
val text = StringEscapeUtils.unescapeEcmaScript(it)
@ -135,8 +160,10 @@ fun String.getAuth(): RequestAuth {
return auth
}
inline fun <T, reified R : Any, O> Array<T>.zip(crossinline mapper: (List<R>) -> O,
crossinline caller: (T) -> R): Single<O> {
inline fun <T, reified R : Any, O> Array<T>.zip(
crossinline mapper: (List<R>) -> O,
crossinline caller: (T) -> R
): Single<O> {
if (isEmpty())
return Single.just(mapper(emptyList()))
val singles = map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) }

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.requests
import com.bumptech.glide.Priority
@ -11,7 +27,11 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.signature.ObjectKey
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER
import com.pitchedapps.frost.facebook.FB_REDIRECT_URL_MATCHER
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.facebook.get
import io.reactivex.Maybe
import okhttp3.Call
import okhttp3.Request
@ -33,9 +53,9 @@ val test: () -> InputStream? = { null }
*/
fun String.getFullSizedImageUrl(url: String): Maybe<String?> = Maybe.fromCallable {
val redirect = requestBuilder().url(url).get().call()
.execute().body()?.string() ?: return@fromCallable null
.execute().body()?.string() ?: return@fromCallable null
return@fromCallable FB_REDIRECT_URL_MATCHER.find(redirect)[1]?.formattedFbUrl
?: return@fromCallable null
?: return@fromCallable null
}.onErrorComplete()
/**
@ -51,7 +71,6 @@ data class HdImageMaybe(val url: String, val cookie: String) {
val isValid: Boolean by lazy {
id != -1L && cookie.isNotBlank()
}
}
/*
@ -69,18 +88,20 @@ class HdImageLoadingFactory : ModelLoaderFactory<HdImageMaybe, InputStream> {
}
fun <T> RequestBuilder<T>.loadWithPotentialHd(model: HdImageMaybe) =
thumbnail(clone().load(model.url))
.load(model)
.apply(RequestOptions().override(Target.SIZE_ORIGINAL))
thumbnail(clone().load(model.url))
.load(model)
.apply(RequestOptions().override(Target.SIZE_ORIGINAL))
class HdImageLoading : ModelLoader<HdImageMaybe, InputStream> {
override fun buildLoadData(model: HdImageMaybe,
width: Int,
height: Int,
options: Options): ModelLoader.LoadData<InputStream>? =
if (!model.isValid) null
else ModelLoader.LoadData(ObjectKey(model), HdImageFetcher(model))
override fun buildLoadData(
model: HdImageMaybe,
width: Int,
height: Int,
options: Options
): ModelLoader.LoadData<InputStream>? =
if (!model.isValid) null
else ModelLoader.LoadData(ObjectKey(model), HdImageFetcher(model))
override fun handles(model: HdImageMaybe) = model.isValid
}
@ -105,7 +126,7 @@ class HdImageFetcher(private val model: HdImageMaybe) : DataFetcher<InputStream>
model.cookie.fbRequest(fail = { callback.fail("Invalid auth") }) {
if (cancelled) return@fbRequest callback.fail("Cancelled")
val url = getFullSizedImage(model.id).invoke()
?: return@fbRequest callback.fail("Null url")
?: return@fbRequest callback.fail("Null url")
if (cancelled) return@fbRequest callback.fail("Cancelled")
if (!url.contains("png") && !url.contains("jpg")) return@fbRequest callback.fail("Invalid format")
urlCall = Request.Builder().url(url).get().call()

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.requests
import com.fasterxml.jackson.annotation.JsonCreator
@ -19,28 +35,27 @@ import java.io.IOException
fun RequestAuth.getMenuData(): FrostRequest<MenuData?> {
val body = listOf(
"fb_dtsg" to fb_dtsg,
"__user" to userId
"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("[", "")
.substringAfter("[", "")
if (jsonString.isBlank()) return null
jsonString = "{ \"data\" : [${StringEscapeUtils.unescapeEcmaScript(jsonString)}"
val mapper = ObjectMapper()
.disable(MapperFeature.AUTO_DETECT_SETTERS)
.disable(MapperFeature.AUTO_DETECT_SETTERS)
return try {
val data = mapper.readValue(jsonString, MenuData::class.java)
@ -48,11 +63,14 @@ fun parseMenu(call: Call): MenuData? {
// parse footer content
val footer = fullString.substringAfter("footerMarkup", "")
.substringAfter("{", "")
.substringBefore("}", "")
.substringAfter("{", "")
.substringBefore("}", "")
val doc = Jsoup.parseBodyFragment(StringEscapeUtils.unescapeEcmaScript(
StringEscapeUtils.unescapeEcmaScript(footer)))
val doc = Jsoup.parseBodyFragment(
StringEscapeUtils.unescapeEcmaScript(
StringEscapeUtils.unescapeEcmaScript(footer)
)
)
val footerData = mutableListOf<MenuFooterItem>()
val footerSmallData = mutableListOf<MenuFooterItem>()
@ -76,11 +94,14 @@ fun parseMenu(call: Call): MenuData? {
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class MenuData(val data: List<MenuHeader> = emptyList(),
val footer: MenuFooter = MenuFooter()) {
data class MenuData(
val data: List<MenuHeader> = emptyList(),
val footer: MenuFooter = MenuFooter()
) {
@JsonCreator constructor(
@JsonProperty("data") data: List<MenuHeader>?
@JsonCreator
constructor(
@JsonProperty("data") data: List<MenuHeader>?
) : this(data ?: emptyList(), MenuFooter())
fun flatMapValid(): List<MenuItemData> {
@ -95,7 +116,6 @@ data class MenuData(val data: List<MenuHeader> = emptyList(),
return items
}
}
interface MenuItemData {
@ -103,17 +123,20 @@ interface MenuItemData {
}
@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 {
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?
@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
@ -121,41 +144,49 @@ data class MenuHeader(val id: String? = null,
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class MenuItem(val id: String? = null,
val name: String? = null,
val pic: String? = null,
val url: String? = null,
val badge: String? = null,
val countDetails: String? = null) : MenuItemData {
data class MenuItem(
val id: String? = null,
val name: String? = null,
val pic: String? = null,
val url: String? = null,
val badge: String? = null,
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") badge: String?,
@JsonProperty("count_details") countDetails: String?,
@JsonProperty("fake") fake: Boolean?
) : this(id, name, pic?.formattedFbUrl,
url?.formattedFbUrl,
if (badge == "0") null else badge,
countDetails)
@JsonCreator
constructor(
@JsonProperty("id") id: String?,
@JsonProperty("name") name: String?,
@JsonProperty("pic") pic: String?,
@JsonProperty("url") url: String?,
@JsonProperty("count") badge: String?,
@JsonProperty("count_details") countDetails: String?,
@JsonProperty("fake") fake: Boolean?
) : this(
id, name, pic?.formattedFbUrl,
url?.formattedFbUrl,
if (badge == "0") null else badge,
countDetails
)
override val isValid: Boolean
get() = !name.isNullOrBlank() && !url.isNullOrBlank()
}
data class MenuFooter(val data: List<MenuFooterItem> = emptyList(),
val smallData: List<MenuFooterItem> = emptyList()) {
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 {
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
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.facebook.FB_URL_BASE
@ -10,10 +26,10 @@ fun RequestAuth.sendMessage(group: String, content: String): FrostRequest<Boolea
// todo test more; only tested against tids=cid...
val body = listOf(
"tids" to group,
"body" to content,
"fb_dtsg" to fb_dtsg,
"__user" to userId
"tids" to group,
"body" to content,
"fb_dtsg" to fb_dtsg,
"__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::validateMessage) {
@ -29,4 +45,4 @@ private fun validateMessage(call: Call): Boolean {
val body = call.execute().body() ?: return false
// todo
return true
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.facebook.FB_URL_BASE
@ -8,11 +24,11 @@ import com.pitchedapps.frost.facebook.FB_URL_BASE
fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> {
val body = listOf(
"click_type" to "notification_click",
"id" to notifId,
"target_id" to "null",
"fb_dtsg" to fb_dtsg,
"__user" to userId
"click_type" to "notification_click",
"id" to notifId,
"target_id" to "null",
"fb_dtsg" to fb_dtsg,
"__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::executeForNoError) {
@ -22,6 +38,6 @@ fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> {
}
fun RequestAuth.markNotificationsRead(vararg notifId: Long) =
notifId.toTypedArray().zip<Long, Boolean, Boolean>(
{ it.all { self -> self } },
{ markNotificationRead(it).invoke() })
notifId.toTypedArray().zip<Long, Boolean, Boolean>(
{ it.all { self -> self } },
{ markNotificationRead(it).invoke() })

View File

@ -1,15 +1,31 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.fragments
import android.content.Context
import android.os.Bundle
import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import ca.allanwang.kau.utils.fadeScaleTransition
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.withArguments
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.contracts.DynamicUiContract
import com.pitchedapps.frost.contracts.FrostContentParent
@ -17,7 +33,12 @@ import com.pitchedapps.frost.contracts.MainActivityContract
import com.pitchedapps.frost.contracts.MainFabContract
import com.pitchedapps.frost.enums.FeedSort
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.ARG_URL
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.REQUEST_REFRESH
import com.pitchedapps.frost.utils.REQUEST_TEXT_ZOOM
import com.pitchedapps.frost.utils.frostEvent
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -33,12 +54,17 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
private const val ARG_POSITION = "arg_position"
private const val ARG_VALID = "arg_valid"
internal operator fun invoke(base: () -> BaseFragment, useFallback: Boolean, data: FbItem, position: Int): BaseFragment {
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,
ARG_POSITION to position
ARG_URL to d.url,
ARG_POSITION to position
)
d.put(fragment.arguments!!)
return fragment
@ -55,8 +81,10 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
if (value || this is WebFragment) return
arguments!!.putBoolean(ARG_VALID, value)
L.e { "Invalidating position $position" }
frostEvent("Native Fallback",
"Item" to baseEnum.name)
frostEvent(
"Native Fallback",
"Item" to baseEnum.name
)
(context as MainActivityContract).reloadFragment(this)
}
@ -75,10 +103,14 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
throw IllegalArgumentException("${this::class.java.simpleName} is not attached to a context implementing MainActivityContract")
}
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
final override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(layoutRes, container, false)
val content = view as? FrostContentParent
?: throw IllegalArgumentException("layoutRes for fragment must return view implementing FrostContentParent")
val content = view as? FrostContentParent
?: throw IllegalArgumentException("layoutRes for fragment must return view implementing FrostContentParent")
this.content = content
content.bind(this)
return view
@ -113,28 +145,28 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
}
override fun attachMainObservable(contract: MainActivityContract): Disposable =
contract.fragmentSubject.observeOn(AndroidSchedulers.mainThread()).subscribe {
when (it) {
REQUEST_REFRESH -> {
core?.apply {
clearHistory()
firstLoad = true
firstLoadRequest()
}
}
position -> {
contract.setTitle(baseEnum.titleId)
updateFab(contract)
core?.active = true
}
-(position + 1) -> {
core?.active = false
}
REQUEST_TEXT_ZOOM -> {
reloadTextSize()
contract.fragmentSubject.observeOn(AndroidSchedulers.mainThread()).subscribe {
when (it) {
REQUEST_REFRESH -> {
core?.apply {
clearHistory()
firstLoad = true
firstLoadRequest()
}
}
position -> {
contract.setTitle(baseEnum.titleId)
updateFab(contract)
core?.active = true
}
-(position + 1) -> {
core?.active = false
}
REQUEST_TEXT_ZOOM -> {
reloadTextSize()
}
}
}
override fun updateFab(contract: MainFabContract) {
contract.hideFab() // default
@ -197,4 +229,3 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
override fun onTabClick(): Unit = content?.core?.onTabClicked() ?: Unit
}

View File

@ -1,6 +1,26 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.fragments
import com.pitchedapps.frost.contracts.*
import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.FrostContentCore
import com.pitchedapps.frost.contracts.FrostContentParent
import com.pitchedapps.frost.contracts.MainActivityContract
import com.pitchedapps.frost.contracts.MainFabContract
import com.pitchedapps.frost.views.FrostRecyclerView
import io.reactivex.disposables.Disposable
@ -74,8 +94,6 @@ interface FragmentContract : FrostContentContainer {
fun onBackPressed(): Boolean
fun onTabClick()
}
interface RecyclerContentContract {
@ -88,5 +106,4 @@ interface RecyclerContentContract {
* Callback returns [true] for success, [false] otherwise
*/
fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.fragments
import ca.allanwang.kau.adapters.fastAdapter
@ -65,7 +81,6 @@ abstract class GenericRecyclerFragment<T, Item : IItem<*, *>> : RecyclerFragment
* 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() {

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.fragments
import com.mikepenz.fastadapter.IItem
@ -6,8 +22,18 @@ import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.parsers.FrostNotifs
import com.pitchedapps.frost.facebook.parsers.NotifParser
import com.pitchedapps.frost.facebook.parsers.ParseResponse
import com.pitchedapps.frost.facebook.requests.*
import com.pitchedapps.frost.iitems.*
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.facebook.requests.MenuItemData
import com.pitchedapps.frost.facebook.requests.fbRequest
import com.pitchedapps.frost.facebook.requests.getMenuData
import com.pitchedapps.frost.iitems.ClickableIItemContract
import com.pitchedapps.frost.iitems.MenuContentIItem
import com.pitchedapps.frost.iitems.MenuFooterIItem
import com.pitchedapps.frost.iitems.MenuFooterSmallIItem
import com.pitchedapps.frost.iitems.MenuHeaderIItem
import com.pitchedapps.frost.iitems.NotificationIItem
import com.pitchedapps.frost.utils.frostJsoup
import com.pitchedapps.frost.views.FrostRecyclerView
import org.jetbrains.anko.doAsync
@ -23,7 +49,7 @@ class NotificationFragment : FrostParserFragment<FrostNotifs, NotificationIItem>
override fun getDoc(cookie: String?) = frostJsoup(cookie, "${FbItem.NOTIFICATIONS.url}?more")
override fun toItems(response: ParseResponse<FrostNotifs>): List<NotificationIItem> =
response.data.notifs.map { NotificationIItem(it, response.cookie) }
response.data.notifs.map { NotificationIItem(it, response.cookie) }
override fun bindImpl(recyclerView: FrostRecyclerView) {
NotificationIItem.bindEvents(adapter)
@ -61,4 +87,4 @@ class MenuFragment : GenericRecyclerFragment<MenuItemData, IItem<*, *>>() {
}
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.fragments
import android.webkit.WebView
@ -43,4 +59,4 @@ class WebFragment : BaseFragment() {
}
super.updateFab(contract)
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.glide
import android.content.Context
@ -30,11 +46,11 @@ object FrostGlide {
}
fun <T> RequestBuilder<T>.transform(vararg transformation: BitmapTransformation): RequestBuilder<T> =
when (transformation.size) {
0 -> this
1 -> apply(RequestOptions.bitmapTransform(transformation[0]))
else -> apply(RequestOptions.bitmapTransform(MultiTransformation(*transformation)))
}
when (transformation.size) {
0 -> this
1 -> apply(RequestOptions.bitmapTransform(transformation[0]))
else -> apply(RequestOptions.bitmapTransform(MultiTransformation(*transformation)))
}
@GlideModule
class FrostGlideModule : AppGlideModule() {
@ -48,7 +64,7 @@ class FrostGlideModule : AppGlideModule() {
}
private fun getFrostHttpClient(): OkHttpClient =
OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build()
OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build()
class FrostCookieInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
@ -58,4 +74,4 @@ class FrostCookieInterceptor : Interceptor {
val request = origRequest.newBuilder().addHeader("Cookie", cookie).build()
return chain.proceed(request)
}
}
}

View File

@ -1,12 +1,32 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.glide
import android.graphics.*
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.pitchedapps.frost.utils.Prefs
import java.security.MessageDigest
/**
* Created by Allan Wang on 27/12/17.
*/
@ -25,17 +45,17 @@ class RoundCornerTransformation : BitmapTransformation() {
bitmap.setHasAlpha(true)
val radius = Math.min(width, height).toFloat() /
(if (Prefs.showRoundedIcons) 2f else 10f)
(if (Prefs.showRoundedIcons) 2f else 10f)
val canvas = Canvas(bitmap)
val paint = Paint()
paint.isAntiAlias = true
paint.shader = BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
canvas.drawRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()),
radius, radius, paint)
canvas.drawRoundRect(
RectF(0f, 0f, width.toFloat(), height.toFloat()),
radius, radius, paint
)
return bitmap
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.iitems
import android.content.Context
@ -32,25 +48,25 @@ interface ClickableIItemContract {
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
}
.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) {
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) {
@ -66,18 +82,18 @@ open class HeaderIItem(val text: String?,
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 {
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) {
@ -93,5 +109,4 @@ open class TextIItem(val text: String?,
text.text = null
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.iitems
import android.view.View
@ -21,9 +37,9 @@ 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 {
class MenuContentIItem(val data: MenuItem) :
KauIItem<MenuContentIItem, MenuContentIItem.ViewHolder>(R.layout.iitem_menu, ::ViewHolder),
ClickableIItemContract {
override val url: String?
get() = data.url
@ -42,9 +58,9 @@ class MenuContentIItem(val data: MenuItem)
val iconUrl = item.data.pic
if (iconUrl != null)
GlideApp.with(itemView)
.load(iconUrl)
.transform(FrostGlide.roundCorner)
.into(icon.visible())
.load(iconUrl)
.transform(FrostGlide.roundCorner)
.into(icon.visible())
else
icon.gone()
content.text = item.data.name
@ -59,12 +75,11 @@ class MenuContentIItem(val data: MenuItem)
}
}
class MenuHeaderIItem(val data: MenuHeader) : HeaderIItem(data.header,
itemId = R.id.item_menu_header)
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)
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)

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.iitems
import android.view.View
@ -26,23 +42,24 @@ import com.pitchedapps.frost.utils.launchWebOverlay
/**
* Created by Allan Wang on 27/12/17.
*/
class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauIItem<NotificationIItem, NotificationIItem.ViewHolder>(
class NotificationIItem(val notification: FrostNotif, val cookie: String) :
KauIItem<NotificationIItem, NotificationIItem.ViewHolder>(
R.layout.iitem_notification, ::ViewHolder
) {
) {
companion object {
fun bindEvents(adapter: ItemAdapter<NotificationIItem>) {
adapter.fastAdapter.withSelectable(false)
.withOnClickListener { v, _, item, position ->
val notif = item.notification
if (notif.unread) {
FrostRunnable.markNotificationRead(v!!.context, notif.id, item.cookie)
adapter.set(position, NotificationIItem(notif.copy(unread = false), item.cookie))
}
// TODO temp fix. If url is dependent, we cannot load it directly
v!!.context.launchWebOverlay(if (notif.url.isIndependent) notif.url else FbItem.NOTIFICATIONS.url)
true
.withOnClickListener { v, _, item, position ->
val notif = item.notification
if (notif.unread) {
FrostRunnable.markNotificationRead(v!!.context, notif.id, item.cookie)
adapter.set(position, NotificationIItem(notif.copy(unread = false), item.cookie))
}
// TODO temp fix. If url is dependent, we cannot load it directly
v!!.context.launchWebOverlay(if (notif.url.isIndependent) notif.url else FbItem.NOTIFICATIONS.url)
true
}
}
//todo see if necessary
@ -52,12 +69,17 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
private class Diff : DiffCallback<NotificationIItem> {
override fun areItemsTheSame(oldItem: NotificationIItem, newItem: NotificationIItem) =
oldItem.notification.id == newItem.notification.id
oldItem.notification.id == newItem.notification.id
override fun areContentsTheSame(oldItem: NotificationIItem, newItem: NotificationIItem) =
oldItem.notification == newItem.notification
oldItem.notification == newItem.notification
override fun getChangePayload(oldItem: NotificationIItem, oldItemPosition: Int, newItem: NotificationIItem, newItemPosition: Int): Any? {
override fun getChangePayload(
oldItem: NotificationIItem,
oldItemPosition: Int,
newItem: NotificationIItem,
newItemPosition: Int
): Any? {
return newItem
}
}
@ -75,15 +97,17 @@ 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.nativeBgColor(notif.unread))
frame.background = createSimpleRippleDrawable(
Prefs.textColor,
Prefs.nativeBgColor(notif.unread)
)
content.setTextColor(Prefs.textColor)
date.setTextColor(Prefs.textColor.withAlpha(150))
val glide = glide
glide.load(notif.img)
.transform(FrostGlide.roundCorner)
.into(avatar)
.transform(FrostGlide.roundCorner)
.into(avatar)
if (notif.thumbnailUrl != null)
glide.load(notif.thumbnailUrl).into(thumbnail.visible())
@ -101,4 +125,4 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
date.text = null
}
}
}
}

View File

@ -1,10 +1,30 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.iitems
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import ca.allanwang.kau.iitems.KauIItem
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.invisible
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.visible
import ca.allanwang.kau.utils.withAlpha
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.IItem
import com.mikepenz.fastadapter_extensions.drag.IDraggable
@ -16,8 +36,8 @@ import com.pitchedapps.frost.utils.Prefs
* Created by Allan Wang on 26/11/17.
*/
class TabIItem(val item: FbItem) : KauIItem<TabIItem, TabIItem.ViewHolder>(
R.layout.iitem_tab_preview,
{ ViewHolder(it) }
R.layout.iitem_tab_preview,
{ ViewHolder(it) }
), IDraggable<TabIItem, IItem<*, *>> {
override fun withIsDraggable(draggable: Boolean): TabIItem = this
@ -45,6 +65,5 @@ class TabIItem(val item: FbItem) : KauIItem<TabIItem, TabIItem.ViewHolder>(
image.setImageDrawable(null)
text.visible().text = null
}
}
}
}

View File

@ -1,14 +1,35 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.injectors
import android.graphics.Color
import android.webkit.WebView
import ca.allanwang.kau.kotlin.lazyContext
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.adjustAlpha
import ca.allanwang.kau.utils.colorToBackground
import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.toRgbaString
import ca.allanwang.kau.utils.use
import ca.allanwang.kau.utils.withAlpha
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.util.*
import java.util.Locale
/**
* Created by Allan Wang on 2017-05-31.
@ -32,17 +53,17 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract {
val bb = Prefs.bgColor.colorToForeground(0.35f)
content = content
.replace("\$T\$", Prefs.textColor.toRgbaString())
.replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString())
.replace("\$A\$", Prefs.accentColor.toRgbaString())
.replace("\$B\$", Prefs.bgColor.toRgbaString())
.replace("\$BT\$", bt)
.replace("\$BBT\$", bb.withAlpha(51).toRgbaString())
.replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
.replace("\$OO\$", bb.withAlpha(255).toRgbaString())
.replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
.replace("\$TI\$", bb.withAlpha(60).toRgbaString())
.replace("\$C\$", bt)
.replace("\$T\$", Prefs.textColor.toRgbaString())
.replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString())
.replace("\$A\$", Prefs.accentColor.toRgbaString())
.replace("\$B\$", Prefs.bgColor.toRgbaString())
.replace("\$BT\$", bt)
.replace("\$BBT\$", bb.withAlpha(51).toRgbaString())
.replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
.replace("\$OO\$", bb.withAlpha(255).toRgbaString())
.replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
.replace("\$TI\$", bb.withAlpha(60).toRgbaString())
.replace("\$C\$", bt)
}
JsBuilder().css(content).build()
} catch (e: FileNotFoundException) {
@ -58,5 +79,4 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract {
fun reset() {
injector.invalidate()
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.injectors
import android.webkit.WebView
@ -9,10 +25,14 @@ import android.webkit.WebView
*/
enum class CssHider(vararg val items: String) : InjectorContract {
CORE("[data-sigil=m_login_upsell]", "[role=progressbar]"),
HEADER("#header", "#mJewelNav", "[data-sigil=MTopBlueBarHeader]",
"#header-notices", "[data-sigil*=m-promo-jewel-header]"),
ADS("article[data-xt*=sponsor]",
"article[data-store*=sponsor]"),
HEADER(
"#header", "#mJewelNav", "[data-sigil=MTopBlueBarHeader]",
"#header-notices", "[data-sigil*=m-promo-jewel-header]"
),
ADS(
"article[data-xt*=sponsor]",
"article[data-store*=sponsor]"
),
PEOPLE_YOU_MAY_KNOW("article._d2r"),
SUGGESTED_GROUPS("article[data-ft*=\"ei\":]"),
COMPOSER("#MComposer"),
@ -22,11 +42,10 @@ enum class CssHider(vararg val items: String) : InjectorContract {
val injector: JsInjector by lazy {
JsBuilder().css("${items.joinToString(separator = ",")}{display:none !important}")
.single(name).build()
.single(name).build()
}
override fun inject(webView: WebView, callback: (() -> Unit)?) {
injector.inject(webView, callback)
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.injectors
import android.webkit.WebView
@ -27,10 +43,9 @@ enum class JsActions(body: String) : InjectorContract {
val function = "(function(){$body})();"
override fun inject(webView: WebView, callback: (() -> Unit)?) =
JsInjector(function).inject(webView, callback)
JsInjector(function).inject(webView, callback)
}
@Suppress("NOTHING_TO_INLINE")
private inline fun clickBySelector(selector: String): String =
"""document.querySelector("$selector").click()"""
"""document.querySelector("$selector").click()"""

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.injectors
import android.webkit.WebView
@ -5,7 +21,7 @@ import ca.allanwang.kau.kotlin.lazyContext
import com.pitchedapps.frost.utils.L
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.util.*
import java.util.Locale
/**
* Created by Allan Wang on 2017-05-31.
@ -31,5 +47,4 @@ enum class JsAssets : InjectorContract {
override fun inject(webView: WebView, callback: (() -> Unit)?) {
injector(webView.context).inject(webView, callback)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.injectors
import android.webkit.WebView
@ -8,7 +24,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.subjects.SingleSubject
import org.apache.commons.text.StringEscapeUtils
import java.util.*
import java.util.Locale
class JsBuilder {
private val css = StringBuilder()
@ -90,10 +106,10 @@ fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Int) -> Uni
}
val observables = Array(validInjectors.size) { SingleSubject.create<Unit>() }
val disposable = Single.zip<Unit, Int>(observables.asList()) { it.size }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { res, _ ->
callback(res)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe { res, _ ->
callback(res)
}
(0 until validInjectors.size).forEach { i ->
validInjectors[i].inject(this) {
observables[i].onSuccess(Unit)
@ -102,8 +118,10 @@ fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Int) -> Uni
return disposable
}
fun FrostWebViewClient.jsInject(vararg injectors: InjectorContract,
callback: ((Int) -> Unit)? = null) = web.jsInject(*injectors, callback = callback)
fun FrostWebViewClient.jsInject(
vararg injectors: InjectorContract,
callback: ((Int) -> Unit)? = null
) = web.jsInject(*injectors, callback = callback)
/**
* Wrapper class to convert a function into an injector
@ -112,4 +130,4 @@ class JsInjector(val function: String) : InjectorContract {
override fun inject(webView: WebView, callback: (() -> Unit)?) {
webView.evaluateJavascript(function) { callback?.invoke() }
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.intro
import android.os.Bundle
@ -17,7 +33,11 @@ class IntroFragmentTheme : BaseIntroFragment(R.layout.intro_theme) {
val themeList
get() = listOf(intro_theme_light, intro_theme_dark, intro_theme_amoled, intro_theme_glass)
override fun viewArray(): Array<Array<out View>> = arrayOf(arrayOf(title), arrayOf(intro_theme_light, intro_theme_dark), arrayOf(intro_theme_amoled, intro_theme_glass))
override fun viewArray(): Array<Array<out View>> = arrayOf(
arrayOf(title),
arrayOf(intro_theme_light, intro_theme_dark),
arrayOf(intro_theme_amoled, intro_theme_glass)
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -40,5 +60,4 @@ class IntroFragmentTheme : BaseIntroFragment(R.layout.intro_theme) {
themeList.forEach { it.animate().scaleXY(if (it == this) 1.6f else 0.8f).start() }
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.intro
import android.graphics.drawable.Drawable
@ -5,7 +21,12 @@ import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindViewResettable
import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.visible
import ca.allanwang.kau.utils.withAlpha
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.Prefs
@ -15,9 +36,9 @@ import com.pitchedapps.frost.utils.launchTabCustomizerActivity
* Created by Allan Wang on 2017-07-28.
*/
abstract class BaseImageIntroFragment(
val titleRes: Int,
val imageRes: Int,
val descRes: Int
val titleRes: Int,
val imageRes: Int,
val descRes: Int
) : BaseIntroFragment(R.layout.intro_image) {
val imageDrawable: LayerDrawable by lazyResettableRegistered { image.drawable as LayerDrawable }
@ -68,7 +89,7 @@ abstract class BaseImageIntroFragment(
}
class IntroAccountFragment : BaseImageIntroFragment(
R.string.intro_multiple_accounts, R.drawable.intro_phone_nav, R.string.intro_multiple_accounts_desc
R.string.intro_multiple_accounts, R.drawable.intro_phone_nav, R.string.intro_multiple_accounts_desc
) {
override fun themeFragmentImpl() {
@ -85,7 +106,7 @@ class IntroAccountFragment : BaseImageIntroFragment(
}
class IntroTabTouchFragment : BaseImageIntroFragment(
R.string.intro_easy_navigation, R.drawable.intro_phone_tab, R.string.intro_easy_navigation_desc
R.string.intro_easy_navigation, R.drawable.intro_phone_tab, R.string.intro_easy_navigation_desc
) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -98,14 +119,20 @@ class IntroTabTouchFragment : BaseImageIntroFragment(
override fun themeFragmentImpl() {
super.themeFragmentImpl()
themeImageComponent(Prefs.iconColor, R.id.intro_phone_icon_1, R.id.intro_phone_icon_2, R.id.intro_phone_icon_3, R.id.intro_phone_icon_4)
themeImageComponent(
Prefs.iconColor,
R.id.intro_phone_icon_1,
R.id.intro_phone_icon_2,
R.id.intro_phone_icon_3,
R.id.intro_phone_icon_4
)
themeImageComponent(Prefs.headerColor, R.id.intro_phone_tab)
themeImageComponent(Prefs.textColor.withAlpha(80), R.id.intro_phone_icon_ripple)
}
}
class IntroTabContextFragment : BaseImageIntroFragment(
R.string.intro_context_aware, R.drawable.intro_phone_long_press, R.string.intro_context_aware_desc
R.string.intro_context_aware, R.drawable.intro_phone_long_press, R.string.intro_context_aware_desc
) {
override fun themeFragmentImpl() {
@ -115,11 +142,16 @@ class IntroTabContextFragment : BaseImageIntroFragment(
themeImageComponent(Prefs.bgColor.colorToForeground(0.2f), R.id.intro_phone_like, R.id.intro_phone_share)
themeImageComponent(Prefs.bgColor.colorToForeground(0.3f), R.id.intro_phone_comment)
themeImageComponent(Prefs.bgColor.colorToForeground(0.1f), R.id.intro_phone_card_1, R.id.intro_phone_card_2)
themeImageComponent(Prefs.textColor, R.id.intro_phone_image_indicator, R.id.intro_phone_comment_indicator, R.id.intro_phone_card_indicator)
themeImageComponent(
Prefs.textColor,
R.id.intro_phone_image_indicator,
R.id.intro_phone_comment_indicator,
R.id.intro_phone_card_indicator
)
}
override fun onPageScrolledImpl(positionOffset: Float) {
super.onPageScrolledImpl(positionOffset)
lastImageFragmentTransition(positionOffset)
}
}
}

View File

@ -1,15 +1,31 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.intro
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.Bundle
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import ca.allanwang.kau.kotlin.LazyResettableRegistry
import ca.allanwang.kau.utils.Kotterknife
import ca.allanwang.kau.utils.bindViewResettable
@ -99,7 +115,6 @@ abstract class BaseIntroFragment(val layoutRes: Int) : Fragment() {
}
protected open fun onPageSelectedImpl() {
}
}
@ -131,4 +146,4 @@ class IntroFragmentEnd : BaseIntroFragment(R.layout.intro_end) {
(activity as IntroActivity).finish(event.x, event.y)
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.rx
import io.reactivex.Single
@ -72,9 +88,9 @@ abstract class RxFlyweight<in T : Any, C : Any, R : Any> {
* you likely won't have a need for flyweights
*/
protected open fun createNewSource(input: T): Single<R> =
Single.fromCallable { call(input) }
.timeout(15, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
Single.fromCallable { call(input) }
.timeout(15, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
fun reset() {
synchronized(lock) {
@ -82,5 +98,4 @@ abstract class RxFlyweight<in T : Any, C : Any, R : Any> {
conditionals.clear()
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.services
import android.app.Notification
@ -26,8 +42,12 @@ import com.pitchedapps.frost.facebook.parsers.NotifParser
import com.pitchedapps.frost.facebook.parsers.ParseNotification
import com.pitchedapps.frost.glide.FrostGlide
import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.utils.*
import java.util.*
import com.pitchedapps.frost.utils.ARG_USER_ID
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostEvent
import com.pitchedapps.frost.utils.isIndependent
import java.util.Locale
/**
* Created by Allan Wang on 2017-07-08.
@ -40,33 +60,38 @@ private val _40_DP = 40.dpToPx
* Enum to handle notification creations
*/
enum class NotificationType(
private val channelId: String,
private val overlayContext: OverlayContext,
private val fbItem: FbItem,
private val parser: FrostParser<ParseNotification>,
private val getTime: (notif: NotificationModel) -> Long,
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String) {
private val channelId: String,
private val overlayContext: OverlayContext,
private val fbItem: FbItem,
private val parser: FrostParser<ParseNotification>,
private val getTime: (notif: NotificationModel) -> Long,
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String
) {
GENERAL(NOTIF_CHANNEL_GENERAL,
OverlayContext.NOTIFICATION,
FbItem.NOTIFICATIONS,
NotifParser,
NotificationModel::epoch,
{ notif, time -> notif.copy(epoch = time) },
Prefs::notificationRingtone) {
GENERAL(
NOTIF_CHANNEL_GENERAL,
OverlayContext.NOTIFICATION,
FbItem.NOTIFICATIONS,
NotifParser,
NotificationModel::epoch,
{ notif, time -> notif.copy(epoch = time) },
Prefs::notificationRingtone
) {
override fun bindRequest(content: NotificationContent, cookie: String) =
FrostRunnable.prepareMarkNotificationRead(content.id, cookie)
FrostRunnable.prepareMarkNotificationRead(content.id, cookie)
},
MESSAGE(NOTIF_CHANNEL_MESSAGES,
OverlayContext.MESSAGE,
FbItem.MESSAGES,
MessageParser,
NotificationModel::epochIm,
{ notif, time -> notif.copy(epochIm = time) },
Prefs::messageRingtone);
MESSAGE(
NOTIF_CHANNEL_MESSAGES,
OverlayContext.MESSAGE,
FbItem.MESSAGES,
MessageParser,
NotificationModel::epochIm,
{ notif, time -> notif.copy(epochIm = time) },
Prefs::messageRingtone
);
private val groupPrefix = "frost_${name.toLowerCase(Locale.CANADA)}"
@ -133,13 +158,15 @@ enum class NotificationType(
}
fun debugNotification(context: Context, data: CookieModel) {
val content = NotificationContent(data,
System.currentTimeMillis(),
"https://github.com/AllanWang/Frost-for-Facebook",
"Debug Notif",
"Test 123",
System.currentTimeMillis() / 1000,
"https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png")
val content = NotificationContent(
data,
System.currentTimeMillis(),
"https://github.com/AllanWang/Frost-for-Facebook",
"Debug Notif",
"Test 123",
System.currentTimeMillis() / 1000,
"https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png"
)
createNotification(context, content).notify(context)
}
@ -147,44 +174,43 @@ enum class NotificationType(
* Create and submit a new notification with the given [content]
*/
private fun createNotification(context: Context, content: NotificationContent): FrostNotification =
with(content) {
val intent = Intent(context, FrostWebActivity::class.java)
// TODO temp fix; we will show notification page for dependent urls. We can trigger a click next time
intent.data = Uri.parse(if (href.isIndependent) href else FbItem.NOTIFICATIONS.url)
intent.putExtra(ARG_USER_ID, data.id)
overlayContext.put(intent)
bindRequest(intent, content, data.cookie)
with(content) {
val intent = Intent(context, FrostWebActivity::class.java)
// TODO temp fix; we will show notification page for dependent urls. We can trigger a click next time
intent.data = Uri.parse(if (href.isIndependent) href else FbItem.NOTIFICATIONS.url)
intent.putExtra(ARG_USER_ID, data.id)
overlayContext.put(intent)
bindRequest(intent, content, data.cookie)
val group = "${groupPrefix}_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notifBuilder = context.frostNotification(channelId)
.setContentTitle(title ?: context.string(R.string.frost_name))
.setContentText(text)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
.setSubText(data.name)
.setGroup(group)
val group = "${groupPrefix}_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notifBuilder = context.frostNotification(channelId)
.setContentTitle(title ?: context.string(R.string.frost_name))
.setContentText(text)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
.setSubText(data.name)
.setGroup(group)
if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
L.v { "Notif load $content" }
if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
L.v { "Notif load $content" }
if (profileUrl != null) {
try {
val profileImg = GlideApp.with(context)
.asBitmap()
.load(profileUrl)
.transform(FrostGlide.circleCrop)
.submit(_40_DP, _40_DP)
.get()
notifBuilder.setLargeIcon(profileImg)
} catch (e: Exception) {
L.e { "Failed to get image $profileUrl" }
}
if (profileUrl != null) {
try {
val profileImg = GlideApp.with(context)
.asBitmap()
.load(profileUrl)
.transform(FrostGlide.circleCrop)
.submit(_40_DP, _40_DP)
.get()
notifBuilder.setLargeIcon(profileImg)
} catch (e: Exception) {
L.e { "Failed to get image $profileUrl" }
}
FrostNotification(group, notifId, notifBuilder)
}
FrostNotification(group, notifId, notifBuilder)
}
/**
* Create a summary notification to wrap the previous ones
@ -198,12 +224,12 @@ enum class NotificationType(
val group = "${groupPrefix}_$userId"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notifBuilder = context.frostNotification(channelId)
.setContentTitle(context.string(R.string.frost_name))
.setContentText("$count ${context.string(fbItem.titleId)}")
.setGroup(group)
.setGroupSummary(true)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
.setContentTitle(context.string(R.string.frost_name))
.setContentText("$count ${context.string(fbItem.titleId)}")
.setGroup(group)
.setGroupSummary(true)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notifBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
@ -211,31 +237,33 @@ enum class NotificationType(
return FrostNotification(group, 1, notifBuilder)
}
}
/**
* Notification data holder
*/
data class NotificationContent(val data: CookieModel,
val id: Long,
val href: String,
val title: String? = null, // defaults to frost title
val text: String,
val timestamp: Long,
val profileUrl: String?) {
data class NotificationContent(
val data: CookieModel,
val id: Long,
val href: String,
val title: String? = null, // defaults to frost title
val text: String,
val timestamp: Long,
val profileUrl: String?
) {
val notifId = Math.abs(id.toInt())
}
/**
* Wrapper for a complete notification builder and identifier
* which can be immediately notified when given a [Context]
*/
data class FrostNotification(private val tag: String,
private val id: Int,
val notif: NotificationCompat.Builder) {
data class FrostNotification(
private val tag: String,
private val id: Int,
val notif: NotificationCompat.Builder
) {
fun withAlert(enable: Boolean, ringtone: String): FrostNotification {
notif.setFrostAlert(enable, ringtone)
@ -243,15 +271,15 @@ data class FrostNotification(private val tag: String,
}
fun notify(context: Context) =
NotificationManagerCompat.from(context).notify(tag, id, notif.build())
NotificationManagerCompat.from(context).notify(tag, id, notif.build())
}
const val NOTIFICATION_PERIODIC_JOB = 7
fun Context.scheduleNotifications(minutes: Long): Boolean =
scheduleJob<NotificationService>(NOTIFICATION_PERIODIC_JOB, minutes)
scheduleJob<NotificationService>(NOTIFICATION_PERIODIC_JOB, minutes)
const val NOTIFICATION_JOB_NOW = 6
fun Context.fetchNotifications(): Boolean =
fetchJob<NotificationService>(NOTIFICATION_JOB_NOW)
fetchJob<NotificationService>(NOTIFICATION_JOB_NOW)

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.services
import android.app.job.JobInfo
@ -37,10 +53,10 @@ private enum class FrostRequestCommands : EnumBundle<FrostRequestCommands> {
}
override fun propagate(bundle: BaseBundle) =
FrostRunnable.prepareMarkNotificationRead(
bundle.getLong(ARG_0),
bundle.getCookie())
FrostRunnable.prepareMarkNotificationRead(
bundle.getLong(ARG_0),
bundle.getCookie()
)
};
override val bundleContract: EnumBundleCompanion<FrostRequestCommands>
@ -58,7 +74,6 @@ private enum class FrostRequestCommands : EnumBundle<FrostRequestCommands> {
abstract fun propagate(bundle: BaseBundle): BaseBundle.() -> Unit
companion object : EnumCompanion<FrostRequestCommands>("frost_arg_commands", values())
}
private const val ARG_COMMAND = "frost_request_command"
@ -99,8 +114,10 @@ object FrostRunnable {
L.d { "Invalid notification id $id for marking as read" }
return false
}
return schedule(context, FrostRequestCommands.NOTIF_READ,
prepareMarkNotificationRead(id, cookie))
return schedule(
context, FrostRequestCommands.NOTIF_READ,
prepareMarkNotificationRead(id, cookie)
)
}
fun propagate(context: Context, intent: Intent?) {
@ -112,9 +129,11 @@ object FrostRunnable {
schedule(context, command, builder)
}
private fun schedule(context: Context,
command: FrostRequestCommands,
bundleBuilder: PersistableBundle.() -> Unit): Boolean {
private fun schedule(
context: Context,
command: FrostRequestCommands,
bundleBuilder: PersistableBundle.() -> Unit
): Boolean {
val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(context, FrostRequestService::class.java)
val bundle = PersistableBundle()
@ -127,10 +146,10 @@ object FrostRunnable {
}
val builder = JobInfo.Builder(JOB_REQUEST_BASE + command.ordinal, serviceComponent)
.setMinimumLatency(0L)
.setExtras(bundle)
.setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setMinimumLatency(0L)
.setExtras(bundle)
.setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
val result = scheduler.schedule(builder.build())
if (result <= 0) {
L.eThrow("FrostRequestService scheduler failed for ${command.name}")
@ -139,7 +158,6 @@ object FrostRunnable {
L.d { "Scheduled ${command.name}" }
return true
}
}
class FrostRequestService : JobService() {
@ -183,4 +201,4 @@ class FrostRequestService : JobService() {
}
return true
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.services
import android.app.job.JobParameters
@ -31,10 +47,12 @@ class NotificationService : JobService() {
override fun onStopJob(params: JobParameters?): Boolean {
val time = System.currentTimeMillis() - startTime
L.d { "Notification service has finished abruptly in $time ms" }
frostEvent("NotificationTime",
"Type" to "Service force stop",
"IM Included" to Prefs.notificationsInstantMessages,
"Duration" to time)
frostEvent(
"NotificationTime",
"Type" to "Service force stop",
"IM Included" to Prefs.notificationsInstantMessages,
"Duration" to time
)
future?.cancel(true)
future = null
return false
@ -43,10 +61,12 @@ class NotificationService : JobService() {
fun finish(params: JobParameters?) {
val time = System.currentTimeMillis() - startTime
L.i { "Notification service has finished in $time ms" }
frostEvent("NotificationTime",
"Type" to "Service",
"IM Included" to Prefs.notificationsInstantMessages,
"Duration" to time)
frostEvent(
"NotificationTime",
"Type" to "Service",
"IM Included" to Prefs.notificationsInstantMessages,
"Duration" to time
)
jobFinished(params, false)
future?.cancel(true)
future = null
@ -61,11 +81,13 @@ class NotificationService : JobService() {
var notifCount = 0
cookies.forEach {
val current = it.id == currentId
if (Prefs.notificationsGeneral
&& (current || Prefs.notificationAllAccounts))
if (Prefs.notificationsGeneral &&
(current || Prefs.notificationAllAccounts)
)
notifCount += fetch(jobId, NotificationType.GENERAL, it)
if (Prefs.notificationsInstantMessages
&& (current || Prefs.notificationsImAllAccounts))
if (Prefs.notificationsInstantMessages &&
(current || Prefs.notificationsImAllAccounts)
)
notifCount += fetch(jobId, NotificationType.MESSAGE, it)
}
@ -99,10 +121,9 @@ class NotificationService : JobService() {
private fun generalNotification(id: Int, textRes: Int, withDefaults: Boolean) {
val notifBuilder = frostNotification(NOTIF_CHANNEL_GENERAL)
.setFrostAlert(withDefaults, Prefs.notificationRingtone)
.setContentTitle(string(R.string.frost_name))
.setContentText(string(textRes))
.setFrostAlert(withDefaults, Prefs.notificationRingtone)
.setContentTitle(string(R.string.frost_name))
.setContentText(string(textRes))
NotificationManagerCompat.from(this).notify(id, notifBuilder.build())
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.services
import android.app.Notification
@ -31,11 +47,11 @@ fun setupNotificationChannels(c: Context) {
val appName = c.string(R.string.frost_name)
val msg = c.string(R.string.messages)
manager.notificationChannels
.filter {
it.id != NOTIF_CHANNEL_GENERAL
&& it.id != NOTIF_CHANNEL_MESSAGES
}
.forEach { manager.deleteNotificationChannel(it.id) }
.filter {
it.id != NOTIF_CHANNEL_GENERAL &&
it.id != NOTIF_CHANNEL_MESSAGES
}
.forEach { manager.deleteNotificationChannel(it.id) }
manager.createNotificationChannel(NOTIF_CHANNEL_GENERAL, appName)
manager.createNotificationChannel(NOTIF_CHANNEL_MESSAGES, "$appName: $msg")
L.d { "Created notification channels: ${manager.notificationChannels.size} channels, ${manager.notificationChannelGroups.size} groups" }
@ -43,8 +59,10 @@ fun setupNotificationChannels(c: Context) {
@RequiresApi(Build.VERSION_CODES.O)
private fun NotificationManager.createNotificationChannel(id: String, name: String): NotificationChannel {
val channel = NotificationChannel(id,
name, NotificationManager.IMPORTANCE_DEFAULT)
val channel = NotificationChannel(
id,
name, NotificationManager.IMPORTANCE_DEFAULT
)
channel.enableLights(true)
channel.lightColor = Prefs.accentColor
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
@ -53,14 +71,14 @@ private fun NotificationManager.createNotificationChannel(id: String, name: Stri
}
fun Context.frostNotification(id: String) =
NotificationCompat.Builder(this, id)
.apply {
setSmallIcon(R.drawable.frost_f_24)
setAutoCancel(true)
setOnlyAlertOnce(true)
setStyle(NotificationCompat.BigTextStyle())
color = color(R.color.frost_notification_accent)
}
NotificationCompat.Builder(this, id)
.apply {
setSmallIcon(R.drawable.frost_f_24)
setAutoCancel(true)
setOnlyAlertOnce(true)
setStyle(NotificationCompat.BigTextStyle())
color = color(R.color.frost_notification_accent)
}
/**
* Dictates whether a notification should have sound/vibration/lights or not
@ -70,8 +88,9 @@ fun Context.frostNotification(id: String) =
fun NotificationCompat.Builder.setFrostAlert(enable: Boolean, ringtone: String): NotificationCompat.Builder {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setGroupAlertBehavior(
if (enable) NotificationCompat.GROUP_ALERT_CHILDREN
else NotificationCompat.GROUP_ALERT_SUMMARY)
if (enable) NotificationCompat.GROUP_ALERT_CHILDREN
else NotificationCompat.GROUP_ALERT_SUMMARY
)
} else if (!enable) {
setDefaults(0)
} else {
@ -111,10 +130,10 @@ inline fun <reified T : JobService> Context.scheduleJob(id: Int, minutes: Long):
if (minutes < 0L) return true
val serviceComponent = ComponentName(this, T::class.java)
val builder = JobInfo.Builder(id, serviceComponent)
.setPeriodic(minutes * 60000)
.setExtras(id)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //TODO add options
.setPeriodic(minutes * 60000)
.setExtras(id)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //TODO add options
val result = scheduler.schedule(builder.build())
if (result <= 0) {
L.eThrow("${T::class.java.simpleName} scheduler failed")
@ -130,10 +149,10 @@ inline fun <reified T : JobService> Context.fetchJob(id: Int): Boolean {
val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(this, T::class.java)
val builder = JobInfo.Builder(id, serviceComponent)
.setMinimumLatency(0L)
.setExtras(id)
.setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setMinimumLatency(0L)
.setExtras(id)
.setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
val result = scheduler.schedule(builder.build())
if (result <= 0) {
L.eThrow("${T::class.java.simpleName} instant scheduler failed")

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.services
import android.content.BroadcastReceiver
@ -18,5 +34,4 @@ class UpdateReceiver : BroadcastReceiver() {
L.d { "Frost has updated" }
context.scheduleNotifications(Prefs.notificationFreq) //Update notifications
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -10,7 +26,16 @@ import com.pitchedapps.frost.activities.SettingsActivity
import com.pitchedapps.frost.enums.MainActivityLayout
import com.pitchedapps.frost.enums.Theme
import com.pitchedapps.frost.injectors.CssAssets
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.REQUEST_NAV
import com.pitchedapps.frost.utils.REQUEST_REFRESH
import com.pitchedapps.frost.utils.REQUEST_TEXT_ZOOM
import com.pitchedapps.frost.utils.frostEvent
import com.pitchedapps.frost.utils.frostNavigationBar
import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.launchTabCustomizerActivity
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostTheme
import com.pitchedapps.frost.views.KPrefTextSeekbar
/**
@ -74,7 +99,6 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
allowCustomAlpha = false
}
colorPicker(R.string.background_color, Prefs::customBackgroundColor, {
Prefs.customBackgroundColor = it
bgCanvas.ripple(it, duration = 500L)
@ -146,17 +170,20 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.tint_nav_desc
}
list.add(KPrefTextSeekbar(
list.add(
KPrefTextSeekbar(
KPrefSeekbar.KPrefSeekbarBuilder(
globalOptions,
R.string.web_text_scaling, Prefs::webTextScaling) {
globalOptions,
R.string.web_text_scaling, Prefs::webTextScaling
) {
Prefs.webTextScaling = it
setFrostResult(REQUEST_TEXT_ZOOM)
}))
})
)
checkbox(R.string.enforce_black_media_bg, Prefs::blackMediaBg, {
Prefs.blackMediaBg = it
}) {
descRes = R.string.enforce_black_media_bg_desc
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -15,7 +31,10 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.fancy_animations_desc
}
checkbox(R.string.overlay_swipe, Prefs::overlayEnabled, { Prefs.overlayEnabled = it; setFrostResult(REQUEST_REFRESH) }) {
checkbox(
R.string.overlay_swipe,
Prefs::overlayEnabled,
{ Prefs.overlayEnabled = it; setFrostResult(REQUEST_REFRESH) }) {
descRes = R.string.overlay_swipe_desc
}
@ -46,5 +65,4 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
checkbox(R.string.analytics, Prefs::analytics, { Prefs.analytics = it }) {
descRes = R.string.analytics_desc
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import android.content.Context
@ -77,25 +93,26 @@ fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = {
}
}
}
}
}
}
private fun Context.createEmail(parser: FrostParser<*>, content: Any?) =
sendFrostEmail("${string(R.string.debug_report)}: ${parser::class.java.simpleName}") {
addItem("Url", parser.url)
addItem("Contents", "$content")
}
sendFrostEmail("${string(R.string.debug_report)}: ${parser::class.java.simpleName}") {
addItem("Url", parser.url)
addItem("Contents", "$content")
}
private const val ZIP_NAME = "debug"
fun SettingsActivity.sendDebug(url: String, html: String?) {
val downloader = OfflineWebsite(url, FbCookie.webCookie ?: "",
baseUrl = FB_URL_BASE,
html = html,
baseDir = DebugActivity.baseDir(this))
val downloader = OfflineWebsite(
url, FbCookie.webCookie ?: "",
baseUrl = FB_URL_BASE,
html = html,
baseDir = DebugActivity.baseDir(this)
)
val md = materialDialog {
title(R.string.parsing_data)
@ -114,7 +131,8 @@ fun SettingsActivity.sendDebug(url: String, html: String?) {
it.dismiss()
if (success) {
val zipUri = it.context.frostUriFromFile(
File(downloader.baseDir, "$ZIP_NAME.zip"))
File(downloader.baseDir, "$ZIP_NAME.zip")
)
L.i { "Sending debug zip with uri $zipUri" }
sendFrostEmail(R.string.debug_report_email_title) {
addItem("Url", url)
@ -128,7 +146,5 @@ fun SettingsActivity.sendDebug(url: String, html: String?) {
}
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import android.util.Log
@ -24,7 +40,6 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
// Experimental content starts here ------------------
// Experimental content ends here --------------------
checkbox(R.string.verbose_logging, Prefs::verboseLogging, {
@ -41,4 +56,4 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
finish()
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -66,4 +82,4 @@ fun SettingsActivity.getFeedPrefs(): KPrefAdapterBuilder.() -> Unit = {
}) {
descRes = R.string.facebook_ads_desc
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -10,8 +26,10 @@ import com.pitchedapps.frost.utils.Prefs
*/
fun SettingsActivity.getNetworkPrefs(): KPrefAdapterBuilder.() -> Unit = {
checkbox(R.string.network_media_on_metered, { !Prefs.loadMediaOnMeteredNetwork }, { Prefs.loadMediaOnMeteredNetwork = !it }) {
checkbox(
R.string.network_media_on_metered,
{ !Prefs.loadMediaOnMeteredNetwork },
{ Prefs.loadMediaOnMeteredNetwork = !it }) {
descRes = R.string.network_media_on_metered_desc
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.settings
import android.annotation.SuppressLint
@ -22,7 +38,6 @@ import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.views.Keywords
/**
* Created by Allan Wang on 2017-06-29.
*/
@ -66,33 +81,33 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
}
checkbox(R.string.notification_general, Prefs::notificationsGeneral,
{
Prefs.notificationsGeneral = it
reloadByTitle(R.string.notification_general_all_accounts)
if (!Prefs.notificationsInstantMessages)
reloadByTitle(R.string.notification_frequency)
}) {
{
Prefs.notificationsGeneral = it
reloadByTitle(R.string.notification_general_all_accounts)
if (!Prefs.notificationsInstantMessages)
reloadByTitle(R.string.notification_frequency)
}) {
descRes = R.string.notification_general_desc
}
checkbox(R.string.notification_general_all_accounts, Prefs::notificationAllAccounts,
{ Prefs.notificationAllAccounts = it }) {
{ Prefs.notificationAllAccounts = it }) {
descRes = R.string.notification_general_all_accounts_desc
enabler = Prefs::notificationsGeneral
}
checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages,
{
Prefs.notificationsInstantMessages = it
reloadByTitle(R.string.notification_messages_all_accounts)
if (!Prefs.notificationsGeneral)
reloadByTitle(R.string.notification_frequency)
}) {
{
Prefs.notificationsInstantMessages = it
reloadByTitle(R.string.notification_messages_all_accounts)
if (!Prefs.notificationsGeneral)
reloadByTitle(R.string.notification_frequency)
}) {
descRes = R.string.notification_messages_desc
}
checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts,
{ Prefs.notificationsImAllAccounts = it }) {
{ Prefs.notificationsImAllAccounts = it }) {
descRes = R.string.notification_messages_all_accounts_desc
enabler = Prefs::notificationsInstantMessages
}
@ -102,15 +117,17 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.notification_channel_desc
onClick = {
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
startActivity(intent)
}
}
} else {
checkbox(R.string.notification_sound, Prefs::notificationSound, {
Prefs.notificationSound = it
reloadByTitle(R.string.notification_ringtone,
R.string.message_ringtone)
reloadByTitle(
R.string.notification_ringtone,
R.string.message_ringtone
)
})
fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) {
@ -118,8 +135,8 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
textGetter = {
if (it.isBlank()) string(R.string.kau_default)
else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it))
?.getTitle(this@getNotificationPrefs)
?: "---" //todo figure out why this happens
?.getTitle(this@getNotificationPrefs)
?: "---" //todo figure out why this happens
}
onClick = {
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
@ -135,20 +152,20 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
}
text(R.string.notification_ringtone, Prefs::notificationRingtone,
{ Prefs.notificationRingtone = it }) {
{ Prefs.notificationRingtone = it }) {
ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE)
}
text(R.string.message_ringtone, Prefs::messageRingtone,
{ Prefs.messageRingtone = it }) {
{ Prefs.messageRingtone = it }) {
ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE)
}
checkbox(R.string.notification_vibrate, Prefs::notificationVibrate,
{ Prefs.notificationVibrate = it })
{ Prefs.notificationVibrate = it })
checkbox(R.string.notification_lights, Prefs::notificationLights,
{ Prefs.notificationLights = it })
{ Prefs.notificationLights = it })
}
if (BuildConfig.DEBUG) {
@ -165,10 +182,9 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.notification_fetch_now_desc
onClick = {
val text =
if (fetchNotifications()) R.string.notification_fetch_success
else R.string.notification_fetch_fail
if (fetchNotifications()) R.string.notification_fetch_success
else R.string.notification_fetch_fail
frostSnackbar(text)
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.content.Context
@ -45,4 +61,4 @@ open class AdBlocker(val assetPath: String) {
if (host.contains(host)) return true
return isAdHost(host.substring(index + 1))
}
}
}

View File

@ -1,8 +1,24 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.graphics.drawable.AnimatedVectorDrawable
import androidx.annotation.DrawableRes
import android.widget.ImageView
import androidx.annotation.DrawableRes
import ca.allanwang.kau.utils.drawable
/**
@ -23,23 +39,23 @@ interface AnimatedVectorContract {
}
class AnimatedVectorDelegate(
/**
* The res for the starting resource; must have parent tag animated-vector
*/
@param:DrawableRes val avdStart: Int,
/**
* The res for the ending resource; must have parent tag animated-vector
*/
@param:DrawableRes val avdEnd: Int,
/**
* The delegate will automatically set the start resource when bound
* If [emitOnBind] is true, it will also trigger the listener
*/
val emitOnBind: Boolean = true,
/**
* The optional listener that will be triggered every time the avd is switched by the delegate
*/
override var animatedVectorListener: ((avd: AnimatedVectorDrawable, forwards: Boolean) -> Unit)? = null
/**
* The res for the starting resource; must have parent tag animated-vector
*/
@param:DrawableRes val avdStart: Int,
/**
* The res for the ending resource; must have parent tag animated-vector
*/
@param:DrawableRes val avdEnd: Int,
/**
* The delegate will automatically set the start resource when bound
* If [emitOnBind] is true, it will also trigger the listener
*/
val emitOnBind: Boolean = true,
/**
* The optional listener that will be triggered every time the avd is switched by the delegate
*/
override var animatedVectorListener: ((avd: AnimatedVectorDrawable, forwards: Boolean) -> Unit)? = null
) : AnimatedVectorContract {
lateinit var view: ImageView
@ -55,9 +71,9 @@ class AnimatedVectorDelegate(
override fun bind(view: ImageView) {
this.view = view
view.context.drawable(avdStart) as? AnimatedVectorDrawable
?: throw IllegalArgumentException("AnimatedVectorDelegate has a starting drawable that isn't an avd")
?: throw IllegalArgumentException("AnimatedVectorDelegate has a starting drawable that isn't an avd")
view.context.drawable(avdEnd) as? AnimatedVectorDrawable
?: throw IllegalArgumentException("AnimatedVectorDelegate has an ending drawable that isn't an avd")
?: throw IllegalArgumentException("AnimatedVectorDelegate has an ending drawable that isn't an avd")
view.setImageResource(avdStart)
if (emitOnBind) animatedVectorListener?.invoke(avd!!, false)
}
@ -70,15 +86,11 @@ class AnimatedVectorDelegate(
private fun animateImpl(toStart: Boolean) {
if ((atStart == toStart)) return L.d { "AVD already at ${if (toStart) "start" else "end"}" }
if (avd == null) return L.d { "AVD null resource" }//no longer using animated vector; do not modify
if (avd == null) return L.d { "AVD null resource" } //no longer using animated vector; do not modify
avd?.stop()
view.setImageResource(if (toStart) avdEnd else avdStart)
animatedVectorListener?.invoke(avd!!, !toStart)
atStart = toStart
avd?.start()
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
object BuildUtils {
@ -18,8 +34,7 @@ object BuildUtils {
}
fun getAllStages(): Array<String> =
arrayOf(BUILD_PRODUCTION, BUILD_TEST, BUILD_GITHUB, BUILD_RELEASE, BUILD_UNNAMED)
arrayOf(BUILD_PRODUCTION, BUILD_TEST, BUILD_GITHUB, BUILD_RELEASE, BUILD_UNNAMED)
fun getStage(build: String): String = build.takeIf { it in getAllStages() } ?: BUILD_UNNAMED
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
/**
@ -15,4 +31,4 @@ const val REQUEST_TEXT_ZOOM = 1 shl 14
const val REQUEST_NAV = 1 shl 15
const val REQUEST_SEARCH = 1 shl 16
const val MAIN_TIMEOUT_DURATION = 30 * 60 * 1000 // 30 min
const val MAIN_TIMEOUT_DURATION = 30 * 60 * 1000 // 30 min

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.app.DownloadManager
@ -16,26 +32,29 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.dbflow.loadFbCookie
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
/**
* Created by Allan Wang on 2017-08-04.
*
* With reference to <a href="https://stackoverflow.com/questions/33434532/android-webview-download-files-like-browsers-do">Stack Overflow</a>
*/
fun Context.frostDownload(url: String?,
userAgent: String = USER_AGENT_BASIC,
contentDisposition: String? = null,
mimeType: String? = null,
contentLength: Long = 0L) {
fun Context.frostDownload(
url: String?,
userAgent: String = USER_AGENT_BASIC,
contentDisposition: String? = null,
mimeType: String? = null,
contentLength: Long = 0L
) {
url ?: return
frostDownload(Uri.parse(url), userAgent, contentDisposition, mimeType, contentLength)
}
fun Context.frostDownload(uri: Uri?,
userAgent: String = USER_AGENT_BASIC,
contentDisposition: String? = null,
mimeType: String? = null,
contentLength: Long = 0L) {
fun Context.frostDownload(
uri: Uri?,
userAgent: String = USER_AGENT_BASIC,
contentDisposition: String? = null,
mimeType: String? = null,
contentLength: Long = 0L
) {
uri ?: return
L.d { "Received download request" }
if (uri.scheme != "http" && uri.scheme != "https") {
@ -75,4 +94,4 @@ fun Context.frostDownload(uri: Uri?,
}
}
private const val DOWNLOAD_MANAGER_PACKAGE = "com.android.providers.downloads"
private const val DOWNLOAD_MANAGER_PACKAGE = "com.android.providers.downloads"

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.content.Intent
@ -39,12 +55,12 @@ interface EnumBundleCompanion<E : Enum<E>> {
operator fun get(bundle: BaseBundle?) = get(bundle?.getString(argTag))
operator fun get(intent: Intent?) = get(intent?.getStringExtra(argTag))
}
open class EnumCompanion<E : Enum<E>>(
final override val argTag: String,
final override val values: Array<E>) : EnumBundleCompanion<E> {
final override val argTag: String,
final override val values: Array<E>
) : EnumBundleCompanion<E> {
final override val valueMap: Map<String, E> = values.map { it.name to it }.toMap()
@ -53,5 +69,4 @@ open class EnumCompanion<E : Enum<E>>(
final override fun get(bundle: BaseBundle?) = super.get(bundle)
final override fun get(intent: Intent?) = super.get(intent)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import org.jsoup.Jsoup
@ -21,8 +37,10 @@ internal fun String.cleanJsoup(): String = Jsoup.clean(this, PrivacyWhitelist())
class PrivacyWhitelist : Whitelist() {
val blacklistAttrs = arrayOf("style", "aria-label", "rel")
val blacklistTags = arrayOf("body", "html", "head", "i", "b", "u", "style", "script",
"br", "p", "span", "ul", "ol", "li")
val blacklistTags = arrayOf(
"body", "html", "head", "i", "b", "u", "style", "script",
"br", "p", "span", "ul", "ol", "li"
)
override fun isSafeAttribute(tagName: String, el: Element, attr: Attribute): Boolean {
val key = attr.key

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.util.Log
@ -5,7 +21,6 @@ import ca.allanwang.kau.logging.KauLogger
import com.bugsnag.android.Bugsnag
import com.pitchedapps.frost.BuildConfig
/**
* Created by Allan Wang on 2017-05-28.
*
@ -45,5 +60,4 @@ object L : KauLogger("Frost", {
Bugsnag.notify(t)
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.graphics.Color
@ -71,8 +87,8 @@ object Prefs : KPref() {
get() = Prefs.bgColor.withAlpha(30)
fun nativeBgColor(unread: Boolean) = Prefs.bgColor
.colorToForeground(if (unread) 0.7f else 0.0f)
.withAlpha(30)
.colorToForeground(if (unread) 0.7f else 0.0f)
.withAlpha(30)
val bgColor: Int
get() = t.bgColor

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import ca.allanwang.kau.kpref.KPref
@ -21,4 +37,3 @@ object Showcase : KPref() {
override fun deleteKeys() = arrayOf("shown_release")
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.annotation.SuppressLint
@ -8,33 +24,59 @@ import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar
import androidx.core.content.FileProvider
import androidx.appcompat.widget.Toolbar
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar
import androidx.core.content.FileProvider
import ca.allanwang.kau.email.EmailBuilder
import ca.allanwang.kau.email.sendEmail
import ca.allanwang.kau.mediapicker.createMediaFile
import ca.allanwang.kau.mediapicker.createPrivateMediaFile
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.adjustAlpha
import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.darken
import ca.allanwang.kau.utils.isColorDark
import ca.allanwang.kau.utils.lighten
import ca.allanwang.kau.utils.navigationBarColor
import ca.allanwang.kau.utils.snackbar
import ca.allanwang.kau.utils.startActivity
import ca.allanwang.kau.utils.startActivityForResult
import ca.allanwang.kau.utils.statusBarColor
import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.with
import ca.allanwang.kau.utils.withAlpha
import ca.allanwang.kau.utils.withMinAlpha
import ca.allanwang.kau.xml.showChangelog
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.SnackbarContentLayout
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.*
import com.pitchedapps.frost.activities.ImageActivity
import com.pitchedapps.frost.activities.LoginActivity
import com.pitchedapps.frost.activities.SelectorActivity
import com.pitchedapps.frost.activities.SettingsActivity
import com.pitchedapps.frost.activities.TabCustomizerActivity
import com.pitchedapps.frost.activities.WebOverlayActivity
import com.pitchedapps.frost.activities.WebOverlayActivityBase
import com.pitchedapps.frost.activities.WebOverlayBasicActivity
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FBCDN_NET
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.FbUrlFormatter.Companion.VIDEO_REDIRECT
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.facebook.formattedFbUrl
import org.apache.commons.text.StringEscapeUtils
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import java.io.File
import java.io.IOException
import java.util.*
import java.util.ArrayList
import java.util.Locale
/**
* Created by Allan Wang on 2017-06-03.
@ -46,7 +88,10 @@ const val ARG_IMAGE_URL = "arg_image_url"
const val ARG_TEXT = "arg_text"
const val ARG_COOKIE = "arg_cookie"
inline fun <reified T : Activity> Context.launchNewTask(cookieList: ArrayList<CookieModel> = arrayListOf(), clearStack: Boolean = false) {
inline fun <reified T : Activity> Context.launchNewTask(
cookieList: ArrayList<CookieModel> = arrayListOf(),
clearStack: Boolean = false
) {
startActivity<T>(clearStack, intentBuilder = {
putParcelableArrayListExtra(EXTRA_COOKIES, cookieList)
})
@ -81,8 +126,10 @@ fun Context.launchWebOverlay(url: String) = launchWebOverlayImpl<WebOverlayActiv
fun Context.launchWebOverlayBasic(url: String) = launchWebOverlayImpl<WebOverlayBasicActivity>(url)
private fun Context.fadeBundle() = ActivityOptions.makeCustomAnimation(this,
android.R.anim.fade_in, android.R.anim.fade_out).toBundle()
private fun Context.fadeBundle() = ActivityOptions.makeCustomAnimation(
this,
android.R.anim.fade_in, android.R.anim.fade_out
).toBundle()
fun Context.launchImageActivity(imageUrl: String, text: String? = null, cookie: String? = null) {
startActivity<ImageActivity>(intentBuilder = {
@ -174,7 +221,6 @@ inline fun Activity.setFrostColors(builder: ActivityThemeUtils.() -> Unit) {
themer.theme(this)
}
fun frostEvent(name: String, vararg events: Pair<String, Any>) {
// todo bind
L.v { "Event: $name ${events.joinToString(", ")}" }
@ -189,9 +235,11 @@ fun Throwable?.logFrostEvent(text: String) {
frostEvent("Errors", "text" to text, "message" to (this?.message ?: "NA"))
}
fun Activity.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) = snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder))
fun Activity.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) =
snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder))
fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) = snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder))
fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) =
snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder))
@SuppressLint("RestrictedApi")
private inline fun frostSnackbar(crossinline builder: Snackbar.() -> Unit): Snackbar.() -> Unit = {
@ -240,7 +288,7 @@ inline val String?.isFacebookUrl
*/
inline val String.isVideoUrl
get() = startsWith(VIDEO_REDIRECT) ||
(startsWith("https://video-") && contains(FBCDN_NET))
(startsWith("https://video-") && contains(FBCDN_NET))
/**
* [true] if url directly leads to a usable image
@ -271,22 +319,22 @@ inline val String?.isIndependent: Boolean
}
val dependentSegments = arrayOf(
"photoset_token", "direct_action_execute", "messages/?pageNum", "sharer.php",
"events/permalink", "events/feed/watch",
/*
* Add new members to groups
*/
"madminpanel",
/**
* Editing images
*/
"/confirmation/?",
/*
* Facebook messages have the following cases for the tid query
* mid* or id* for newer threads, which can be launched in new windows
* or a hash for old threads, which must be loaded on old threads
*/
"messages/read/?tid=id", "messages/read/?tid=mid"
"photoset_token", "direct_action_execute", "messages/?pageNum", "sharer.php",
"events/permalink", "events/feed/watch",
/*
* Add new members to groups
*/
"madminpanel",
/**
* Editing images
*/
"/confirmation/?",
/*
* Facebook messages have the following cases for the tid query
* mid* or id* for newer threads, which can be launched in new windows
* or a hash for old threads, which must be loaded on old threads
*/
"messages/read/?tid=id", "messages/read/?tid=mid"
)
inline val String?.isExplicitIntent
@ -297,16 +345,20 @@ fun Context.frostChangelog() = showChangelog(R.xml.frost_changelog, Prefs.textCo
}
fun Context.frostUriFromFile(file: File): Uri =
FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".provider",
file)
FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + ".provider",
file
)
inline fun Context.sendFrostEmail(@StringRes subjectId: Int, crossinline builder: EmailBuilder.() -> Unit) = sendFrostEmail(string(subjectId), builder)
inline fun Context.sendFrostEmail(@StringRes subjectId: Int, crossinline builder: EmailBuilder.() -> Unit) =
sendFrostEmail(string(subjectId), builder)
inline fun Context.sendFrostEmail(subjectId: String, crossinline builder: EmailBuilder.() -> Unit) = sendEmail(string(R.string.dev_email), subjectId) {
builder()
addFrostDetails()
}
inline fun Context.sendFrostEmail(subjectId: String, crossinline builder: EmailBuilder.() -> Unit) =
sendEmail(string(R.string.dev_email), subjectId) {
builder()
addFrostDetails()
}
fun EmailBuilder.addFrostDetails() {
addItem("Prev version", Prefs.prevVersionCode.toString())
@ -318,7 +370,8 @@ fun EmailBuilder.addFrostDetails() {
fun frostJsoup(url: String) = frostJsoup(FbCookie.webCookie, url)
fun frostJsoup(cookie: String?, url: String) = Jsoup.connect(url).cookie(FACEBOOK_COM, cookie).userAgent(USER_AGENT_BASIC).get()!!
fun frostJsoup(cookie: String?, url: String) =
Jsoup.connect(url).cookie(FACEBOOK_COM, cookie).userAgent(USER_AGENT_BASIC).get()!!
fun Element.first(vararg select: String): Element? {
select.forEach {
@ -346,6 +399,6 @@ fun File.createFreshDir(): Boolean {
}
fun String.unescapeHtml(): String =
StringEscapeUtils.unescapeXml(this)
.replace("\\u003C", "<")
.replace("\\\"", "\"")
StringEscapeUtils.unescapeXml(this)
.replace("\\u003C", "<")
.replace("\\\"", "\"")

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.utils
import android.content.Context
@ -41,7 +57,9 @@ class WebContext(val unformattedUrl: String, val text: String?) {
enum class WebContextType(val textId: Int, val onClick: (c: Context, wc: WebContext) -> Unit) {
OPEN_LINK(R.string.open_link, { c, wc -> c.launchWebOverlay(wc.unformattedUrl) }),
COPY_LINK(R.string.copy_link, { c, wc -> c.copyToClipboard(wc.url) }),
COPY_TEXT(R.string.copy_text, { c, wc -> if (wc.text != null) c.copyToClipboard(wc.text) else c.toast(R.string.no_text) }),
COPY_TEXT(
R.string.copy_text,
{ c, wc -> if (wc.text != null) c.copyToClipboard(wc.text) else c.toast(R.string.no_text) }),
SHARE_LINK(R.string.share_link, { c, wc -> c.shareText(wc.url) }),
DEBUG_LINK(R.string.debug_link, { c, wc ->
c.materialDialogThemed {
@ -63,4 +81,4 @@ enum class WebContextType(val textId: Int, val onClick: (c: Context, wc: WebCont
val values = values()
operator fun get(index: Int) = values[index]
}
}
}

View File

@ -1,12 +1,32 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.graphics.drawable.Drawable
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.iitems.KauIItem
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.invisible
import ca.allanwang.kau.utils.toDrawable
import ca.allanwang.kau.utils.visible
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
@ -23,7 +43,7 @@ import com.pitchedapps.frost.utils.Prefs
* Created by Allan Wang on 2017-06-05.
*/
class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.ViewHolder>
(R.layout.view_account, { ViewHolder(it) }, R.id.item_account) {
(R.layout.view_account, { ViewHolder(it) }, R.id.item_account) {
override fun bindView(viewHolder: ViewHolder, payloads: MutableList<Any>) {
super.bindView(viewHolder, payloads)
@ -33,20 +53,37 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
if (cookie != null) {
text.text = cookie.name
GlideApp.with(itemView).load(profilePictureUrl(cookie.id))
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
text.fadeIn()
return false
}
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
text.fadeIn()
return false
}
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
text.fadeIn()
return false
}
}).into(image)
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
text.fadeIn()
return false
}
}).into(image)
} else {
text.visible()
image.setImageDrawable(GoogleMaterial.Icon.gmd_add_circle_outline.toDrawable(itemView.context, 100, Prefs.textColor))
image.setImageDrawable(
GoogleMaterial.Icon.gmd_add_circle_outline.toDrawable(
itemView.context,
100,
Prefs.textColor
)
)
text.text = itemView.context.getString(R.string.kau_add_account)
}
}
@ -64,4 +101,4 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
val image: ImageView by bindView(R.id.account_image)
val text: AppCompatTextView by bindView(R.id.account_text)
}
}
}

View File

@ -1,27 +1,50 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.content.Context
import android.graphics.drawable.GradientDrawable
import androidx.constraintlayout.widget.ConstraintLayout
import android.util.AttributeSet
import ca.allanwang.kau.utils.*
import androidx.constraintlayout.widget.ConstraintLayout
import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.dpToPx
import ca.allanwang.kau.utils.gone
import ca.allanwang.kau.utils.toDrawable
import ca.allanwang.kau.utils.visible
import ca.allanwang.kau.utils.withAlpha
import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.Prefs
import kotlinx.android.synthetic.main.view_badged_icon.view.*
/**
* Created by Allan Wang on 2017-06-19.
*/
class BadgedIcon @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
init {
inflate(context, R.layout.view_badged_icon, this)
val badgeColor = Prefs.mainActivityLayout.backgroundColor().withAlpha(255).colorToForeground(0.2f)
val badgeBackground = GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, intArrayOf(badgeColor, badgeColor))
val badgeBackground =
GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, intArrayOf(badgeColor, badgeColor))
badgeBackground.cornerRadius = 13.dpToPx.toFloat()
badge_text.background = badgeBackground
badge_text.setTextColor(Prefs.mainActivityLayout.iconColor())
@ -30,7 +53,13 @@ class BadgedIcon @JvmOverloads constructor(
var iicon: IIcon? = null
set(value) {
field = value
badge_image.setImageDrawable(value?.toDrawable(context, sizeDp = 20, color = Prefs.mainActivityLayout.iconColor()))
badge_image.setImageDrawable(
value?.toDrawable(
context,
sizeDp = 20,
color = Prefs.mainActivityLayout.iconColor()
)
)
}
fun setAllAlpha(alpha: Float) {
@ -46,5 +75,4 @@ class BadgedIcon @JvmOverloads constructor(
if (value != null && value != "0") badge_text.visible()
else badge_text.gone()
}
}
}

View File

@ -1,13 +1,36 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.content.Context
import android.os.Build
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ProgressBar
import ca.allanwang.kau.utils.*
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.circularReveal
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
import ca.allanwang.kau.utils.invisibleIf
import ca.allanwang.kau.utils.isVisible
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.withAlpha
import com.pitchedapps.frost.R
import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.FrostContentCore
@ -24,25 +47,32 @@ import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
class FrostContentWeb @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrostContentView<FrostWebView>(context, attrs, defStyleAttr, defStyleRes) {
override val layoutRes: Int = R.layout.view_content_base_web
}
class FrostContentRecycler @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrostContentView<FrostRecyclerView>(context, attrs, defStyleAttr, defStyleRes) {
override val layoutRes: Int = R.layout.view_content_base_recycler
}
abstract class FrostContentView<out T> @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes),
FrostContentParent where T : View, T : FrostContentCore {
FrostContentParent where T : View, T : FrostContentCore {
private val refresh: SwipeRefreshLayout by bindView(R.id.content_refresh)
private val progress: ProgressBar by bindView(R.id.content_progress)
@ -88,15 +118,14 @@ abstract class FrostContentView<out T> @JvmOverloads constructor(
}.addTo(compositeDisposable)
refreshObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
refresh.isRefreshing = it
refresh.isEnabled = true
}.addTo(compositeDisposable)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
refresh.isRefreshing = it
refresh.isEnabled = true
}.addTo(compositeDisposable)
refresh.setOnRefreshListener { coreView.reload(true) }
reloadThemeSelf()
}
override fun bind(container: FrostContentContainer) {
@ -151,24 +180,24 @@ abstract class FrostContentView<out T> @JvmOverloads constructor(
var loading = dispose != null
dispose?.dispose()
dispose = refreshObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it) {
loading = true
transitionStart = System.currentTimeMillis()
clearAnimation()
if (isVisible)
fadeOut(duration = 200L)
} else if (loading) {
loading = false
if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
dispose?.dispose()
dispose = null
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it) {
loading = true
transitionStart = System.currentTimeMillis()
clearAnimation()
if (isVisible)
fadeOut(duration = 200L)
} else if (loading) {
loading = false
if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
dispose?.dispose()
dispose = null
}
}
}
return true
}
}
}

View File

@ -1,10 +1,26 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.utils.circularReveal
import ca.allanwang.kau.utils.fadeOut
import com.pitchedapps.frost.contracts.FrostContentContainer
@ -18,9 +34,11 @@ import com.pitchedapps.frost.utils.Prefs
*
*/
class FrostRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr),
FrostContentCore {
FrostContentCore {
override fun reload(animate: Boolean) = reloadBase(animate)
@ -102,5 +120,4 @@ class FrostRecyclerView @JvmOverloads constructor(
override fun reloadTextSizeSelf() {
// todo
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.annotation.SuppressLint
@ -27,7 +43,9 @@ import com.pitchedapps.frost.utils.Prefs
* Parent must have layout with both height & width as match_parent
*/
class FrostVideoView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : VideoView(context, attrs, defStyleAttr) {
/**
@ -105,7 +123,10 @@ class FrostVideoView @JvmOverloads constructor(
videoDimensions.set(dimen, dimen)
}
val portrait = height > width
val scale = Math.min(height / (if (portrait) 4f else 2.3f) / videoDimensions.y, width / (if (portrait) 2.3f else 4f) / videoDimensions.x)
val scale = Math.min(
height / (if (portrait) 4f else 2.3f) / videoDimensions.y,
width / (if (portrait) 2.3f else 4f) / videoDimensions.x
)
val desiredHeight = scale * videoDimensions.y
val desiredWidth = scale * videoDimensions.x
val padding = containerContract.lowerVideoPadding
@ -151,8 +172,8 @@ class FrostVideoView @JvmOverloads constructor(
/**
* Only remap if not expanded and if dimensions have changed
*/
val shouldRemap = !isExpanded
&& (videoDimensions.x != ratio * intrinsicWidth || videoDimensions.y != ratio * intrinsicHeight)
val shouldRemap = !isExpanded &&
(videoDimensions.x != ratio * intrinsicWidth || videoDimensions.y != ratio * intrinsicHeight)
videoDimensions.set(ratio * intrinsicWidth, ratio * intrinsicHeight)
if (shouldRemap) updateLocation()
}
@ -226,7 +247,8 @@ class FrostVideoView @JvmOverloads constructor(
* -------------------------------------------------------------------
*/
private inner class FrameTouchListener(context: Context) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
private inner class FrameTouchListener(context: Context) : GestureDetector.SimpleOnGestureListener(),
View.OnTouchListener {
private val gestureDetector: GestureDetector = GestureDetector(context, this)
@ -252,7 +274,8 @@ class FrostVideoView @JvmOverloads constructor(
/**
* Monitors the view click events to show and hide the video controls if they have been specified.
*/
private inner class VideoTouchListener(context: Context) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
private inner class VideoTouchListener(context: Context) : GestureDetector.SimpleOnGestureListener(),
View.OnTouchListener {
private val gestureDetector: GestureDetector = GestureDetector(context, this)
private val downLoc = PointF()
@ -315,4 +338,4 @@ class FrostVideoView @JvmOverloads constructor(
return true
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.content.Context
@ -8,11 +24,22 @@ import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
import ca.allanwang.kau.utils.gone
import ca.allanwang.kau.utils.goneIf
import ca.allanwang.kau.utils.inflate
import ca.allanwang.kau.utils.isColorDark
import ca.allanwang.kau.utils.isGone
import ca.allanwang.kau.utils.isVisible
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.setMenuIcons
import ca.allanwang.kau.utils.visible
import ca.allanwang.kau.utils.withMinAlpha
import com.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostDownload
import kotlinx.android.synthetic.main.view_video.view.*
@ -21,7 +48,9 @@ import kotlinx.android.synthetic.main.view_video.view.*
* Created by Allan Wang on 2017-10-13.
*/
class FrostVideoViewer @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), FrostVideoViewerContract {
companion object {
@ -51,16 +80,18 @@ class FrostVideoViewer @JvmOverloads constructor(
inflate(R.layout.view_video, true)
alpha = 0f
video_background.setBackgroundColor(
if (!Prefs.blackMediaBg && Prefs.bgColor.isColorDark)
Prefs.bgColor.withMinAlpha(200)
else
Color.BLACK)
if (!Prefs.blackMediaBg && Prefs.bgColor.isColorDark)
Prefs.bgColor.withMinAlpha(200)
else
Color.BLACK
)
video.setViewerContract(this)
video.pause()
video_toolbar.inflateMenu(R.menu.menu_video)
context.setMenuIcons(video_toolbar.menu, Prefs.iconColor,
R.id.action_pip to GoogleMaterial.Icon.gmd_picture_in_picture_alt,
R.id.action_download to GoogleMaterial.Icon.gmd_file_download
context.setMenuIcons(
video_toolbar.menu, Prefs.iconColor,
R.id.action_pip to GoogleMaterial.Icon.gmd_picture_in_picture_alt,
R.id.action_download to GoogleMaterial.Icon.gmd_file_download
)
video_toolbar.setOnMenuItemClickListener {
when (it.itemId) {
@ -141,7 +172,6 @@ class FrostVideoViewer @JvmOverloads constructor(
if (!video_toolbar.isGone)
video_toolbar.fadeOut(duration = CONTROL_ANIMATION_DURATION) { video_toolbar.gone() }
}
}
interface FrostVideoViewerContract : VideoControlsVisibilityListener {
@ -171,4 +201,4 @@ interface FrostVideoContainerContract {
* Called once the video has stopped & should be removed
*/
fun onVideoFinished()
}
}

View File

@ -1,10 +1,26 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.annotation.SuppressLint
import android.content.Context
import androidx.viewpager.widget.ViewPager
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
import com.pitchedapps.frost.utils.Prefs
/**
@ -12,21 +28,22 @@ import com.pitchedapps.frost.utils.Prefs
*
* Basic override to allow us to control swiping
*/
class FrostViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {
class FrostViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ViewPager(context, attrs) {
var enableSwipe = true
override fun onInterceptTouchEvent(ev: MotionEvent?) =
try {
Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev)
} catch (e: IllegalArgumentException) {
false
}
try {
Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev)
} catch (e: IllegalArgumentException) {
false
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent?): Boolean =
try {
Prefs.viewpagerSwipe && enableSwipe && super.onTouchEvent(ev)
} catch (e: IllegalArgumentException) {
false
}
}
try {
Prefs.viewpagerSwipe && enableSwipe && super.onTouchEvent(ev)
} catch (e: IllegalArgumentException) {
false
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.animation.ValueAnimator
@ -16,16 +32,22 @@ import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostDownload
import com.pitchedapps.frost.web.*
import com.pitchedapps.frost.web.FrostChromeClient
import com.pitchedapps.frost.web.FrostJSI
import com.pitchedapps.frost.web.FrostWebViewClient
import com.pitchedapps.frost.web.NestedWebView
import com.pitchedapps.frost.web.shouldUseBasicAgent
/**
* Created by Allan Wang on 2017-05-29.
*
*/
class FrostWebView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : NestedWebView(context, attrs, defStyleAttr),
FrostContentCore {
FrostContentCore {
override fun reload(animate: Boolean) {
if (parent.registerTransition(false, animate))
@ -59,7 +81,6 @@ class FrostWebView @JvmOverloads constructor(
return this
}
/**
* Wrapper to the main userAgentString to cache it.
* This decouples it from the UiThread
@ -168,4 +189,4 @@ class FrostWebView @JvmOverloads constructor(
super.destroy()
}
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.annotation.SuppressLint
@ -43,4 +59,4 @@ class KPrefTextSeekbar(builder: KPrefSeekbarContract) : KPrefSeekbar(builder) {
holder.desc?.setTextSize(TypedValue.COMPLEX_UNIT_PX, descOriginalSize)
super.unbindView(holder)
}
}
}

View File

@ -1,15 +1,31 @@
/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.views
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.appcompat.widget.AppCompatEditText
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatEditText
import androidx.appcompat.widget.AppCompatTextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.tint
@ -23,12 +39,13 @@ import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.Prefs
/**
* Created by Allan Wang on 2017-06-19.
*/
class Keywords @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
val editText: AppCompatEditText by bindView(R.id.edit_text)
@ -51,7 +68,8 @@ class Keywords @JvmOverloads constructor(
recycler.layoutManager = LinearLayoutManager(context)
recycler.adapter = adapter
adapter.withEventHook(object : ClickEventHook<KeywordItem>() {
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? KeywordItem.ViewHolder)?.delete
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? =
(viewHolder as? KeywordItem.ViewHolder)?.delete
override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<KeywordItem>, item: KeywordItem) {
adapter.remove(position)
@ -62,7 +80,6 @@ class Keywords @JvmOverloads constructor(
fun save() {
Prefs.notificationKeywords = adapter.adapterItems.mapTo(mutableSetOf()) { it.keyword }
}
}
private fun IIcon.keywordDrawable(context: Context): Drawable = toDrawable(context, 20, Prefs.textColor)
@ -94,4 +111,4 @@ class KeywordItem(val keyword: String) : AbstractItem<KeywordItem, KeywordItem.V
delete.setImageDrawable(GoogleMaterial.Icon.gmd_delete.keywordDrawable(itemView.context))
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More