1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-08 12:02:33 +01: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.getkeepsafe.dexcount'
apply plugin: 'com.gladed.androidgitversion' apply plugin: 'com.gladed.androidgitversion'
apply from: '../spotless.gradle'
android { android {
compileSdkVersion kau.targetSdk compileSdkVersion kau.targetSdk
buildToolsVersion kau.buildTools 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 package com.pitchedapps.frost
import android.app.Activity import android.app.Activity
@ -21,7 +37,11 @@ import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.glide.GlideApp import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.services.scheduleNotifications import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.services.setupNotificationChannels 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.DatabaseConfig
import com.raizlabs.android.dbflow.config.FlowConfig import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager import com.raizlabs.android.dbflow.config.FlowManager
@ -30,10 +50,9 @@ import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins import io.reactivex.plugins.RxJavaPlugins
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import java.util.Random
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
* Created by Allan Wang on 2017-05-28. * Created by Allan Wang on 2017-05-28.
*/ */
@ -46,10 +65,12 @@ class FrostApp : Application() {
// lateinit var refWatcher: RefWatcher // lateinit var refWatcher: RefWatcher
private fun FlowConfig.Builder.withDatabase(name: String, klass: KClass<*>) = private fun FlowConfig.Builder.withDatabase(name: String, klass: KClass<*>) =
addDatabaseConfig(DatabaseConfig.builder(klass.java) addDatabaseConfig(
.databaseName(name) DatabaseConfig.builder(klass.java)
.modelNotifier(ContentResolverNotifier("${BuildConfig.APPLICATION_ID}.dbflow.provider")) .databaseName(name)
.build()) .modelNotifier(ContentResolverNotifier("${BuildConfig.APPLICATION_ID}.dbflow.provider"))
.build()
)
override fun onCreate() { override fun onCreate() {
if (!buildIsLollipopAndUp) { // not supported if (!buildIsLollipopAndUp) { // not supported
@ -57,11 +78,13 @@ class FrostApp : Application() {
return return
} }
FlowManager.init(FlowConfig.Builder(this) FlowManager.init(
FlowConfig.Builder(this)
.withDatabase(CookiesDb.NAME, CookiesDb::class) .withDatabase(CookiesDb.NAME, CookiesDb::class)
.withDatabase(FbTabsDb.NAME, FbTabsDb::class) .withDatabase(FbTabsDb.NAME, FbTabsDb::class)
.withDatabase(NotificationDb.NAME, NotificationDb::class) .withDatabase(NotificationDb.NAME, NotificationDb::class)
.build()) .build()
)
Showcase.initialize(this, "${BuildConfig.APPLICATION_ID}.showcase") Showcase.initialize(this, "${BuildConfig.APPLICATION_ID}.showcase")
Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs") Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs")
// if (LeakCanary.isInAnalyzerProcess(this)) return // if (LeakCanary.isInAnalyzerProcess(this)) return
@ -95,9 +118,11 @@ class FrostApp : Application() {
val c = imageView.context val c = imageView.context
val request = GlideApp.with(c) val request = GlideApp.with(c)
val old = request.load(uri).apply(RequestOptions().placeholder(placeholder)) val old = request.load(uri).apply(RequestOptions().placeholder(placeholder))
request.load(uri).apply(RequestOptions() request.load(uri).apply(
.signature(ApplicationVersionSignature.obtain(c))) RequestOptions()
.thumbnail(old).into(imageView) .signature(ApplicationVersionSignature.obtain(c))
)
.thumbnail(old).into(imageView)
} }
}) })
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
@ -127,7 +152,6 @@ class FrostApp : Application() {
L.e(it) { "RxJava error" } L.e(it) { "RxJava error" }
} }
} }
} }
private fun initBugsnag() { private fun initBugsnag() {
@ -136,7 +160,7 @@ class FrostApp : Application() {
Bugsnag.disableExceptionHandler() Bugsnag.disableExceptionHandler()
if (!BuildConfig.APPLICATION_ID.startsWith("com.pitchedapps.frost")) return if (!BuildConfig.APPLICATION_ID.startsWith("com.pitchedapps.frost")) return
val version = BuildUtils.match(BuildConfig.VERSION_NAME) 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.enableExceptionHandler()
Bugsnag.setNotifyReleaseStages(*BuildUtils.getAllStages()) Bugsnag.setNotifyReleaseStages(*BuildUtils.getAllStages())
Bugsnag.setAppVersion(version.versionName) 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 package com.pitchedapps.frost
import android.content.Intent 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.L
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.launchNewTask 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. * Created by Allan Wang on 2017-05-28.
@ -46,7 +63,8 @@ class StartActivity : KauBaseActivity() {
if (Prefs.userId != -1L) if (Prefs.userId != -1L)
startActivity<MainActivity>(intentBuilder = { startActivity<MainActivity>(intentBuilder = {
putParcelableArrayListExtra(EXTRA_COOKIES, cookies) 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 else
launchNewTask<SelectorActivity>(cookies) launchNewTask<SelectorActivity>(cookies)
@ -57,11 +75,10 @@ class StartActivity : KauBaseActivity() {
} catch (e: Exception) { } catch (e: Exception) {
showInvalidWebView() showInvalidWebView()
} }
} }
private fun showInvalidWebView() = private fun showInvalidWebView() =
showInvalidView(R.string.error_webview) showInvalidView(R.string.error_webview)
private fun showInvalidSdkView() { private fun showInvalidSdkView() {
val text = try { val text = try {
@ -73,12 +90,12 @@ class StartActivity : KauBaseActivity() {
} }
private fun showInvalidView(textRes: Int) = private fun showInvalidView(textRes: Int) =
showInvalidView(string(textRes)) showInvalidView(string(textRes))
private fun showInvalidView(text: String) { private fun showInvalidView(text: String) {
setContentView(R.layout.activity_invalid) setContentView(R.layout.activity_invalid)
findViewById<ImageView>(R.id.invalid_icon) 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 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 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.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView 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.AboutActivityBase
import ca.allanwang.kau.about.LibraryIItem import ca.allanwang.kau.about.LibraryIItem
import ca.allanwang.kau.adapters.FastItemThemedAdapter import ca.allanwang.kau.adapters.FastItemThemedAdapter
import ca.allanwang.kau.adapters.ThemableIItem import ca.allanwang.kau.adapters.ThemableIItem
import ca.allanwang.kau.adapters.ThemableIItemDelegate 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.Libs
import com.mikepenz.aboutlibraries.entity.Library import com.mikepenz.aboutlibraries.entity.Library
import com.mikepenz.aboutlibraries.entity.License 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.L
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
/** /**
* Created by Allan Wang on 2017-06-26. * Created by Allan Wang on 2017-06-26.
*/ */
@ -42,21 +64,21 @@ class AboutActivity : AboutActivityBase(null, {
override fun getLibraries(libs: Libs): List<Library> { override fun getLibraries(libs: Libs): List<Library> {
val include = arrayOf( val include = arrayOf(
"AboutLibraries", "AboutLibraries",
"AndroidIconics", "AndroidIconics",
"androidin_appbillingv3", "androidin_appbillingv3",
"androidslidinguppanel", "androidslidinguppanel",
"Crashlytics", "Crashlytics",
"dbflow", "dbflow",
"fastadapter", "fastadapter",
"glide", "glide",
"Jsoup", "Jsoup",
"kau", "kau",
"kotterknife", "kotterknife",
"materialdialogs", "materialdialogs",
"materialdrawer", "materialdrawer",
"rxjava", "rxjava",
"subsamplingscaleimageview" "subsamplingscaleimageview"
) )
val l = libs.prepareLibraries(this, include, null, false, true, true) val l = libs.prepareLibraries(this, include, null, false, true, true)
@ -136,11 +158,11 @@ class AboutActivity : AboutActivityBase(null, {
val c = itemView.context val c = itemView.context
val size = c.dimenPixelSize(R.dimen.kau_avatar_bounds) val size = c.dimenPixelSize(R.dimen.kau_avatar_bounds)
images = arrayOf<Pair<IIcon, () -> Unit>>( images = arrayOf<Pair<IIcon, () -> Unit>>(
GoogleMaterial.Icon.gmd_arrow_downward to { c.startLink(R.string.github_downloads_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.Icon2.cmd_reddit to { c.startLink(R.string.reddit_url) },
CommunityMaterial.Icon.cmd_github_circle to { c.startLink(R.string.github_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_slack to { c.startLink(R.string.slack_url) },
CommunityMaterial.Icon2.cmd_xda to { c.startLink(R.string.xda_url) } CommunityMaterial.Icon2.cmd_xda to { c.startLink(R.string.xda_url) }
).mapIndexed { i, (icon, onClick) -> ).mapIndexed { i, (icon, onClick) ->
ImageView(c).apply { ImageView(c).apply {
layoutParams = ViewGroup.LayoutParams(size, size) layoutParams = ViewGroup.LayoutParams(size, size)
@ -154,10 +176,16 @@ class AboutActivity : AboutActivityBase(null, {
} }
val set = ConstraintSet() val set = ConstraintSet()
set.clone(container) set.clone(container)
set.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, set.createHorizontalChain(ConstraintSet.PARENT_ID,
images.map { it.id }.toIntArray(), null, ConstraintSet.CHAIN_SPREAD_INSIDE) ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
ConstraintSet.RIGHT,
images.map { it.id }.toIntArray(),
null,
ConstraintSet.CHAIN_SPREAD_INSIDE
)
set.applyTo(container) 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 package com.pitchedapps.frost.activities
import android.content.res.Configuration import android.content.res.Configuration
@ -80,7 +96,6 @@ abstract class BaseActivity : KauBaseActivity() {
//// disposeNetworkConnectivity() //// disposeNetworkConnectivity()
// } // }
override fun onStop() { override fun onStop() {
if (this is VideoViewHolder) videoOnStop() if (this is VideoViewHolder) videoOnStop()
super.onStop() super.onStop()
@ -90,4 +105,4 @@ abstract class BaseActivity : KauBaseActivity() {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
if (this is VideoViewHolder) videoViewer?.updateLocation() 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 package com.pitchedapps.frost.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -8,25 +24,31 @@ import android.graphics.PointF
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle 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.Menu
import android.view.MenuItem import android.view.MenuItem
import android.webkit.ValueCallback import android.webkit.ValueCallback
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.webkit.WebView import android.webkit.WebView
import android.widget.FrameLayout 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.SearchItem
import ca.allanwang.kau.searchview.SearchView import ca.allanwang.kau.searchview.SearchView
import ca.allanwang.kau.searchview.SearchViewHolder import ca.allanwang.kau.searchview.SearchViewHolder
import ca.allanwang.kau.searchview.bindSearchView 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.Builder
import co.zsmb.materialdrawerkt.builders.accountHeader import co.zsmb.materialdrawerkt.builders.accountHeader
import co.zsmb.materialdrawerkt.builders.drawer 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.divider
import co.zsmb.materialdrawerkt.draweritems.profile.profile import co.zsmb.materialdrawerkt.draweritems.profile.profile
import co.zsmb.materialdrawerkt.draweritems.profile.profileSetting 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.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.IIcon 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.facebook.profilePictureUrl
import com.pitchedapps.frost.fragments.BaseFragment import com.pitchedapps.frost.fragments.BaseFragment
import com.pitchedapps.frost.fragments.WebFragment 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.BadgedIcon
import com.pitchedapps.frost.views.FrostVideoViewer import com.pitchedapps.frost.views.FrostVideoViewer
import com.pitchedapps.frost.views.FrostViewPager 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 * Most of the logic that is unrelated to handling fragments
*/ */
abstract class BaseMainActivity : BaseActivity(), MainActivityContract, abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
FileChooserContract by FileChooserDelegate(), FileChooserContract by FileChooserDelegate(),
VideoViewHolder, SearchViewHolder { VideoViewHolder, SearchViewHolder {
protected lateinit var adapter: SectionsPagerAdapter protected lateinit var adapter: SectionsPagerAdapter
override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper) override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper)
@ -111,12 +155,14 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
Prefs.versionCode = BuildConfig.VERSION_CODE Prefs.versionCode = BuildConfig.VERSION_CODE
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
frostChangelog() frostChangelog()
frostEvent("Version", frostEvent(
"Version code" to BuildConfig.VERSION_CODE, "Version",
"Prev version code" to Prefs.prevVersionCode, "Version code" to BuildConfig.VERSION_CODE,
"Version name" to BuildConfig.VERSION_NAME, "Prev version code" to Prefs.prevVersionCode,
"Build type" to BuildConfig.BUILD_TYPE, "Version name" to BuildConfig.VERSION_NAME,
"Frost id" to Prefs.frostId) "Build type" to BuildConfig.BUILD_TYPE,
"Frost id" to Prefs.frostId
)
} }
} }
setupDrawer(savedInstanceState) setupDrawer(savedInstanceState)
@ -204,7 +250,9 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
identifier = -2L identifier = -2L
} }
profileSetting(nameRes = R.string.kau_add_account) { 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() textColor = Prefs.textColor.toLong()
identifier = -3L identifier = -3L
} }
@ -225,8 +273,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
} else { } else {
materialDialogThemed { materialDialogThemed {
title(R.string.kau_logout) title(R.string.kau_logout)
content(String.format(string(R.string.kau_logout_confirm_as_x), currentCookie.name content(
?: Prefs.userId.toString())) String.format(
string(R.string.kau_logout_confirm_as_x), currentCookie.name
?: Prefs.userId.toString()
)
)
positiveText(R.string.kau_yes) positiveText(R.string.kau_yes)
negativeText(R.string.kau_no) negativeText(R.string.kau_no)
onPositive { _, _ -> FbCookie.logout(this@BaseMainActivity) } onPositive { _, _ -> FbCookie.logout(this@BaseMainActivity) }
@ -295,9 +347,11 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu) menuInflater.inflate(R.menu.menu_main, menu)
toolbar.tint(Prefs.iconColor) toolbar.tint(Prefs.iconColor)
setMenuIcons(menu, Prefs.iconColor, setMenuIcons(
R.id.action_settings to GoogleMaterial.Icon.gmd_settings, menu, Prefs.iconColor,
R.id.action_search to GoogleMaterial.Icon.gmd_search) R.id.action_settings to GoogleMaterial.Icon.gmd_settings,
R.id.action_search to GoogleMaterial.Icon.gmd_search
)
searchViewBindIfNull { searchViewBindIfNull {
bindSearchView(menu, R.id.action_search, Prefs.iconColor) { bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
textCallback = { query, searchView -> textCallback = { query, searchView ->
@ -309,7 +363,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
if (data != null) { if (data != null) {
val items = data.mapTo(mutableListOf(), FrostSearch::toSearchItem) val items = data.mapTo(mutableListOf(), FrostSearch::toSearchItem)
if (items.isNotEmpty()) 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 searchViewCache[query] = items
searchView.results = items searchView.results = items
} }
@ -332,7 +392,8 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
R.id.action_settings -> { R.id.action_settings -> {
val intent = Intent(this, SettingsActivity::class.java) val intent = Intent(this, SettingsActivity::class.java)
intent.putParcelableArrayListExtra(EXTRA_COOKIES, cookies()) 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) startActivityForResult(intent, ACTIVITY_SETTINGS, bundle)
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
@ -340,7 +401,10 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
return true 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) openMediaPicker(filePathCallback, fileChooserParams)
} }
@ -377,8 +441,10 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
adapter.forcedFallbacks.clear() adapter.forcedFallbacks.clear()
adapter.forcedFallbacks.addAll(savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK) adapter.forcedFallbacks.addAll(
?: emptyList()) savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK)
?: emptyList()
)
} }
override fun onResume() { override fun onResume() {
@ -444,10 +510,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
val item = pages[position] val item = pages[position]
return BaseFragment(item.fragmentCreator, return BaseFragment(
forcedFallbacks.contains(item.name), item.fragmentCreator,
item, forcedFallbacks.contains(item.name),
position) item,
position
)
} }
override fun getCount() = pages.size 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 getPageTitle(position: Int): CharSequence = getString(pages[position].titleId)
override fun getItemPosition(fragment: Any) = override fun getItemPosition(fragment: Any) =
if (fragment !is BaseFragment) if (fragment !is BaseFragment)
POSITION_UNCHANGED POSITION_UNCHANGED
else if (fragment is WebFragment || fragment.valid) else if (fragment is WebFragment || fragment.valid)
POSITION_UNCHANGED POSITION_UNCHANGED
else else
POSITION_NONE POSITION_NONE
} }
override val lowerVideoPadding: PointF override val lowerVideoPadding: PointF
@ -469,4 +537,4 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
PointF(0f, toolbar.height.toFloat()) PointF(0f, toolbar.height.toFloat())
else else
PointF(0f, 0f) 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 package com.pitchedapps.frost.activities
import android.app.Activity 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.setIcon
import ca.allanwang.kau.utils.visible import ca.allanwang.kau.utils.visible
import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.injectors.JsActions import com.pitchedapps.frost.injectors.JsActions
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.createFreshDir import com.pitchedapps.frost.utils.createFreshDir
@ -73,23 +89,22 @@ class DebugActivity : KauBaseActivity() {
val body = it[1] as? String val body = it[1] as? String
screenshot to body screenshot to body
}.observeOn(AndroidSchedulers.mainThread()) }.observeOn(AndroidSchedulers.mainThread())
.subscribe { (screenshot, body), err -> .subscribe { (screenshot, body), err ->
if (err != null) { if (err != null) {
L.e { "DebugActivity error ${err.message}" } L.e { "DebugActivity error ${err.message}" }
setResult(Activity.RESULT_CANCELED) 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() 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 { override fun onSupportNavigateUp(): Boolean {
@ -113,4 +128,4 @@ class DebugActivity : KauBaseActivity() {
else else
super.onBackPressed() 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 package com.pitchedapps.frost.activities
import android.content.Intent import android.content.Intent
@ -5,16 +21,25 @@ import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import com.google.android.material.floatingactionbutton.FloatingActionButton
import android.view.View import android.view.View
import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.internal.KauBaseActivity
import ca.allanwang.kau.logging.KauLoggerExtension import ca.allanwang.kau.logging.KauLoggerExtension
import ca.allanwang.kau.mediapicker.scanMedia import ca.allanwang.kau.mediapicker.scanMedia
import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
import ca.allanwang.kau.permissions.kauRequestPermissions 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.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.R 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.call
import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl
import com.pitchedapps.frost.facebook.requests.requestBuilder 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 com.sothree.slidinguppanel.SlidingUpPanelLayout
import kotlinx.android.synthetic.main.activity_image.* import kotlinx.android.synthetic.main.activity_image.*
import okhttp3.Response import okhttp3.Response
@ -34,7 +71,8 @@ import java.io.File
import java.io.FileFilter import java.io.FileFilter
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
/** /**
* Created by Allan Wang on 2017-07-15. * 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 // a unique image identifier based on the id (if it exists), and its hash
private val imageHash: String by lazy { private val imageHash: String by lazy {
"${Math.abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() "${Math.abs(
?: 0)}_${Math.abs(imageUrl.hashCode())}" FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode()
?: 0
)}_${Math.abs(imageUrl.hashCode())}"
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -105,11 +145,15 @@ class ImageActivity : KauBaseActivity() {
L.v { "Displaying image $imageUrl" } L.v { "Displaying image $imageUrl" }
val layout = if (!imageText.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless val layout = if (!imageText.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless
setContentView(layout) setContentView(layout)
image_container.setBackgroundColor(if (Prefs.blackMediaBg) Color.BLACK image_container.setBackgroundColor(
else Prefs.bgColor.withMinAlpha(222)) if (Prefs.blackMediaBg) Color.BLACK
else Prefs.bgColor.withMinAlpha(222)
)
image_text?.setTextColor(if (Prefs.blackMediaBg) Color.WHITE else Prefs.textColor) image_text?.setTextColor(if (Prefs.blackMediaBg) Color.WHITE else Prefs.textColor)
image_text?.setBackgroundColor((if (Prefs.blackMediaBg) Color.BLACK else Prefs.bgColor) image_text?.setBackgroundColor(
.colorToForeground(0.2f).withAlpha(255)) (if (Prefs.blackMediaBg) Color.BLACK else Prefs.bgColor)
.colorToForeground(0.2f).withAlpha(255)
)
image_text?.text = imageText image_text?.text = imageText
image_progress.tint(if (Prefs.blackMediaBg) Color.WHITE else Prefs.accentColor) image_progress.tint(if (Prefs.blackMediaBg) Color.WHITE else Prefs.accentColor)
image_panel?.addPanelSlideListener(object : SlidingUpPanelLayout.SimplePanelSlideListener() { image_panel?.addPanelSlideListener(object : SlidingUpPanelLayout.SimplePanelSlideListener() {
@ -208,16 +252,15 @@ class ImageActivity : KauBaseActivity() {
} }
private fun getImageResponse(): Response = cookie.requestBuilder() private fun getImageResponse(): Response = cookie.requestBuilder()
.url(trueImageUrl) .url(trueImageUrl)
.get() .get()
.call() .call()
.execute() .execute()
@Throws(IOException::class) @Throws(IOException::class)
private fun downloadImageTo(file: File) { private fun downloadImageTo(file: File) {
val body = getImageResponse().body() val body = getImageResponse().body()
?: throw IOException("Failed to retrieve image body") ?: throw IOException("Failed to retrieve image body")
body.byteStream().use { input -> body.byteStream().use { input ->
file.outputStream().use { output -> file.outputStream().use { output ->
input.copyTo(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) { ERROR(GoogleMaterial.Icon.gmd_error, Color.WHITE, Color.RED) {
override fun onClick(activity: ImageActivity) { override fun onClick(activity: ImageActivity) {
activity.materialDialogThemed { activity.materialDialogThemed {
@ -334,5 +381,4 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
} }
abstract fun onClick(activity: ImageActivity) 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 package com.pitchedapps.frost.activities
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle 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.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.Button import android.widget.Button
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView 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.internal.KauBaseActivity
import ca.allanwang.kau.ui.views.RippleCanvas import ca.allanwang.kau.ui.views.RippleCanvas
import ca.allanwang.kau.ui.widgets.InkPageIndicator 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.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R 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.Prefs
import com.pitchedapps.frost.utils.cookies import com.pitchedapps.frost.utils.cookies
import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.launchNewTask
import org.jetbrains.anko.find import org.jetbrains.anko.find
/** /**
* Created by Allan Wang on 2017-07-25. * Created by Allan Wang on 2017-07-25.
* *
@ -43,12 +72,12 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
private var barHasNext = true private var barHasNext = true
val fragments = listOf( val fragments = listOf(
IntroFragmentWelcome(), IntroFragmentWelcome(),
IntroFragmentTheme(), IntroFragmentTheme(),
IntroAccountFragment(), IntroAccountFragment(),
IntroTabTouchFragment(), IntroTabTouchFragment(),
IntroTabContextFragment(), IntroTabContextFragment(),
IntroFragmentEnd() IntroFragmentEnd()
) )
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -97,7 +126,6 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
page.alpha = 1f page.alpha = 1f
page.translationX = 0f page.translationX = 0f
} }
} }
fun finish(x: Float, y: Float) { fun finish(x: Float, y: Float) {
@ -107,9 +135,11 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
postDelayed(1000) { finish() } postDelayed(1000) { finish() }
} }
val lastView: View? = fragments.last().view val lastView: View? = fragments.last().view
arrayOf<View?>(skip, indicator, next, arrayOf<View?>(
lastView?.find(R.id.intro_title), skip, indicator, next,
lastView?.find(R.id.intro_desc)).forEach { lastView?.find(R.id.intro_title),
lastView?.find(R.id.intro_desc)
).forEach {
it?.animate()?.alpha(0f)?.setDuration(600)?.start() it?.animate()?.alpha(0f)?.setDuration(600)?.start()
} }
if (Prefs.textColor != Color.WHITE) { if (Prefs.textColor != Color.WHITE) {
@ -147,7 +177,6 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
} }
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {
} }
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: 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 if (barHasNext == hasNext) return
barHasNext = hasNext barHasNext = hasNext
next.fadeScaleTransition { 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) 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 getItem(position: Int): Fragment = fragments[position]
override fun getCount(): Int = fragments.size 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 package com.pitchedapps.frost.activities
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import android.widget.ImageView
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.Toolbar 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.bindView
import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut 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.FrostGlide
import com.pitchedapps.frost.glide.GlideApp import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.glide.transform 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 com.pitchedapps.frost.web.LoginWebView
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import io.reactivex.subjects.SingleSubject import io.reactivex.subjects.SingleSubject
/** /**
* Created by Allan Wang on 2017-06-01. * Created by Allan Wang on 2017-06-01.
*/ */
@ -78,51 +98,62 @@ class LoginActivity : BaseActivity() {
private fun loadInfo(cookie: CookieModel) { private fun loadInfo(cookie: CookieModel) {
refresh = true refresh = true
Single.zip<Boolean, String, Pair<Boolean, String>>( Single.zip<Boolean, String, Pair<Boolean, String>>(
profileSubject, profileSubject,
usernameSubject, usernameSubject,
BiFunction(::Pair)) BiFunction(::Pair)
.observeOn(AndroidSchedulers.mainThread()).subscribe { (foundImage, name) -> )
refresh = false .observeOn(AndroidSchedulers.mainThread()).subscribe { (foundImage, name) ->
if (!foundImage) { refresh = false
L.e { "Could not get profile photo; Invalid userId?" } if (!foundImage) {
L._i { cookie } L.e { "Could not get profile photo; Invalid userId?" }
} L._i { cookie }
textview.text = String.format(getString(R.string.welcome), name) }
textview.fadeIn() textview.text = String.format(getString(R.string.welcome), name)
frostEvent("Login", "success" to true) 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 * 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) loadFbCookiesAsync {
Handler().postDelayed({ val cookies = ArrayList(it)
if (Showcase.intro) Handler().postDelayed({
launchNewTask<IntroActivity>(cookies, true) if (Showcase.intro)
else launchNewTask<IntroActivity>(cookies, true)
launchNewTask<MainActivity>(cookies, true) else
}, 1000) launchNewTask<MainActivity>(cookies, true)
} }, 1000)
}.disposeOnDestroy() }
}.disposeOnDestroy()
loadProfile(cookie.id) loadProfile(cookie.id)
loadUsername(cookie) loadUsername(cookie)
} }
private fun loadProfile(id: Long) { private fun loadProfile(id: Long) {
profileLoader.load(profilePictureUrl(id)) profileLoader.load(profilePictureUrl(id))
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> { .transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { override fun onResourceReady(
profileSubject.onSuccess(true) resource: Drawable?,
return false 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 { override fun onLoadFailed(
e.logFrostEvent("Profile loading exception") e: GlideException?,
profileSubject.onSuccess(false) model: Any?,
return false target: Target<Drawable>?,
} isFirstResource: Boolean
}).into(profile) ): Boolean {
e.logFrostEvent("Profile loading exception")
profileSubject.onSuccess(false)
return false
}
}).into(profile)
} }
private fun loadUsername(cookie: CookieModel) { private fun loadUsername(cookie: CookieModel) {
@ -146,5 +177,4 @@ class LoginActivity : BaseActivity() {
web.pauseTimers() web.pauseTimers()
super.onPause() 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 package com.pitchedapps.frost.activities
import android.os.Bundle import android.os.Bundle
import com.google.android.material.tabs.TabLayout
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.BadgedIcon
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
@ -36,19 +52,19 @@ class MainActivity : BaseMainActivity() {
super.onPageScrolled(position, positionOffset, positionOffsetPixels) super.onPageScrolled(position, positionOffset, positionOffsetPixels)
val delta = positionOffset * (255 - 128).toFloat() val delta = positionOffset * (255 - 128).toFloat()
tabsForEachView { tabPosition, view -> tabsForEachView { tabPosition, view ->
view.setAllAlpha(when (tabPosition) { view.setAllAlpha(
position -> 255.0f - delta when (tabPosition) {
position + 1 -> 128.0f + delta position -> 255.0f - delta
else -> 128f position + 1 -> 128.0f + delta
}) else -> 128f
}
)
} }
} }
}) })
viewPager.post { fragmentSubject.onNext(0); lastPosition = 0 } //trigger hook so title is set viewPager.post { fragmentSubject.onNext(0); lastPosition = 0 } //trigger hook so title is set
} }
private fun setupTabs() { private fun setupTabs() {
viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))
tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) { tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) {
@ -63,31 +79,31 @@ class MainActivity : BaseMainActivity() {
} }
}) })
headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS) headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS)
.subscribeOn(Schedulers.newThread()) .subscribeOn(Schedulers.newThread())
.map { Jsoup.parse(it) } .map { Jsoup.parse(it) }
.filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist .filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist
.map { .map {
val feed = it.select("[data-sigil*=feed] [data-sigil=count]") val feed = it.select("[data-sigil*=feed] [data-sigil=count]")
val requests = it.select("[data-sigil*=requests] [data-sigil=count]") val requests = it.select("[data-sigil*=requests] [data-sigil=count]")
val messages = it.select("[data-sigil*=messages] [data-sigil=count]") val messages = it.select("[data-sigil*=messages] [data-sigil=count]")
val notifications = it.select("[data-sigil*=notifications] [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() } return@map arrayOf(feed, requests, messages, notifications).map { e -> e?.getOrNull(0)?.ownText() }
} }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { (feed, requests, messages, notifications) -> .subscribe { (feed, requests, messages, notifications) ->
tabsForEachView { _, view -> tabsForEachView { _, view ->
when (view.iicon) { when (view.iicon) {
FbItem.FEED.icon -> view.badgeText = feed FbItem.FEED.icon -> view.badgeText = feed
FbItem.FRIENDS.icon -> view.badgeText = requests FbItem.FRIENDS.icon -> view.badgeText = requests
FbItem.MESSAGES.icon -> view.badgeText = messages FbItem.MESSAGES.icon -> view.badgeText = messages
FbItem.NOTIFICATIONS.icon -> view.badgeText = notifications FbItem.NOTIFICATIONS.icon -> view.badgeText = notifications
}
} }
}.disposeOnDestroy() }
}.disposeOnDestroy()
adapter.pages.forEach { adapter.pages.forEach {
tabs.addTab(tabs.newTab() 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 package com.pitchedapps.frost.activities
import android.os.Bundle import android.os.Bundle
import androidx.constraintlayout.widget.ConstraintLayout import android.view.View
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View
import ca.allanwang.kau.utils.bindView import ca.allanwang.kau.utils.bindView
import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
@ -47,4 +63,4 @@ class SelectorActivity : BaseActivity() {
background(container) 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 package com.pitchedapps.frost.activities
import android.annotation.SuppressLint 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.KPrefActivity
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.ui.views.RippleCanvas 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.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R import com.pitchedapps.frost.R
import com.pitchedapps.frost.enums.Support import com.pitchedapps.frost.enums.Support
import com.pitchedapps.frost.settings.* import com.pitchedapps.frost.settings.getAppearancePrefs
import com.pitchedapps.frost.utils.* 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. * Created by Allan Wang on 2017-06-06.
@ -146,7 +181,6 @@ class SettingsActivity : KPrefActivity() {
iicon = CommunityMaterial.Icon.cmd_android_debug_bridge iicon = CommunityMaterial.Icon.cmd_android_debug_bridge
visible = { Prefs.debugSettings } visible = { Prefs.debugSettings }
} }
} }
fun shouldRestartMain() { fun shouldRestartMain() {
@ -179,9 +213,11 @@ class SettingsActivity : KPrefActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_settings, menu) menuInflater.inflate(R.menu.menu_settings, menu)
toolbar.tint(Prefs.iconColor) toolbar.tint(Prefs.iconColor)
setMenuIcons(menu, Prefs.iconColor, setMenuIcons(
R.id.action_email to GoogleMaterial.Icon.gmd_email, menu, Prefs.iconColor,
R.id.action_changelog to GoogleMaterial.Icon.gmd_info) R.id.action_email to GoogleMaterial.Icon.gmd_email,
R.id.action_changelog to GoogleMaterial.Icon.gmd_info
)
return true return true
} }
@ -201,4 +237,4 @@ class SettingsActivity : KPrefActivity() {
fun setFrostResult(flag: Int) { fun setFrostResult(flag: Int) {
resultFlag = resultFlag or flag 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 package com.pitchedapps.frost.activities
import android.app.Activity import android.app.Activity
@ -5,21 +21,18 @@ import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.kotlin.lazyContext import ca.allanwang.kau.kotlin.lazyContext
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.scaleXY import ca.allanwang.kau.utils.scaleXY
import ca.allanwang.kau.utils.setIcon import ca.allanwang.kau.utils.setIcon
import com.pitchedapps.frost.R
import ca.allanwang.kau.utils.withAlpha import ca.allanwang.kau.utils.withAlpha
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
import com.mikepenz.fastadapter_extensions.drag.ItemTouchCallback import com.mikepenz.fastadapter_extensions.drag.ItemTouchCallback
import com.mikepenz.fastadapter_extensions.drag.SimpleDragCallback import com.mikepenz.fastadapter_extensions.drag.SimpleDragCallback
import com.mikepenz.google_material_typeface_library.GoogleMaterial 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.TAB_COUNT
import com.pitchedapps.frost.dbflow.loadFbTabs import com.pitchedapps.frost.dbflow.loadFbTabs
import com.pitchedapps.frost.dbflow.save 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.Prefs
import com.pitchedapps.frost.utils.setFrostColors import com.pitchedapps.frost.utils.setFrostColors
import kotlinx.android.synthetic.main.activity_tab_customizer.* import kotlinx.android.synthetic.main.activity_tab_customizer.*
import java.util.* import java.util.Collections
/** /**
* Created by Allan Wang on 26/11/17. * Created by Allan Wang on 26/11/17.
@ -96,9 +109,9 @@ class TabCustomizerActivity : BaseActivity() {
override fun itemTouchDropped(oldPosition: Int, newPosition: Int) = Unit override fun itemTouchDropped(oldPosition: Int, newPosition: Int) = Unit
} }
private class TabDragCallback( private class TabDragCallback(
directions: Int, itemTouchCallback: ItemTouchCallback directions: Int,
itemTouchCallback: ItemTouchCallback
) : SimpleDragCallback(directions, itemTouchCallback) { ) : SimpleDragCallback(directions, itemTouchCallback) {
private var draggingView: TabIItem.ViewHolder? = null 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 package com.pitchedapps.frost.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -5,25 +21,52 @@ import android.content.Intent
import android.graphics.PointF import android.graphics.PointF
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.appcompat.widget.Toolbar
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.webkit.ValueCallback import android.webkit.ValueCallback
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.widget.FrameLayout 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.kauSwipeOnCreate
import ca.allanwang.kau.swipe.kauSwipeOnDestroy 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.google.android.material.snackbar.BaseTransientBottomBar
import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R 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.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.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.FrostContentWeb
import com.pitchedapps.frost.views.FrostVideoViewer import com.pitchedapps.frost.views.FrostVideoViewer
import com.pitchedapps.frost.views.FrostWebView import com.pitchedapps.frost.views.FrostWebView
@ -31,7 +74,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import okhttp3.HttpUrl import okhttp3.HttpUrl
/** /**
* Created by Allan Wang on 2017-06-01. * Created by Allan Wang on 2017-06-01.
* *
@ -103,8 +145,8 @@ class WebOverlayActivity : WebOverlayActivityBase(false)
@SuppressLint("Registered") @SuppressLint("Registered")
open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseActivity(), open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseActivity(),
ActivityContract, FrostContentContainer, ActivityContract, FrostContentContainer,
VideoViewHolder, FileChooserContract by FileChooserDelegate() { VideoViewHolder, FileChooserContract by FileChooserDelegate() {
override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper) override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper)
val toolbar: Toolbar by bindView(R.id.overlay_toolbar) 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.bind(this)
content.titleObservable content.titleObservable
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { toolbar.title = it } .subscribe { toolbar.title = it }
.disposeOnDestroy() .disposeOnDestroy()
with(web) { with(web) {
if (forceBasicAgent) //todo check; the webview already adds it dynamically if (forceBasicAgent) //todo check; the webview already adds it dynamically
@ -235,7 +277,10 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
kauSwipeOnDestroy() kauSwipeOnDestroy()
} }
override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams) { override fun openFileChooser(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
) {
openMediaPicker(filePathCallback, fileChooserParams) openMediaPicker(filePathCallback, fileChooserParams)
} }
@ -247,9 +292,11 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
menuInflater.inflate(R.menu.menu_web, menu) menuInflater.inflate(R.menu.menu_web, menu)
overlayContext?.onMenuCreate(this, menu) overlayContext?.onMenuCreate(this, menu)
toolbar.tint(Prefs.iconColor) toolbar.tint(Prefs.iconColor)
setMenuIcons(menu, Prefs.iconColor, setMenuIcons(
R.id.action_share to CommunityMaterial.Icon2.cmd_share, menu, Prefs.iconColor,
R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy) R.id.action_share to CommunityMaterial.Icon2.cmd_share,
R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy
)
return true return true
} }
@ -270,5 +317,4 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
*/ */
override var videoViewer: FrostVideoViewer? = null override var videoViewer: FrostVideoViewer? = null
override val lowerVideoPadding: PointF = PointF(0f, 0f) 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 package com.pitchedapps.frost.contracts
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
@ -25,4 +41,4 @@ interface MainActivityContract : ActivityContract, MainFabContract {
interface MainFabContract { interface MainFabContract {
fun showFab(iicon: IIcon, clickEvent: () -> Unit) fun showFab(iicon: IIcon, clickEvent: () -> Unit)
fun hideFab() 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 package com.pitchedapps.frost.contracts
/** /**
@ -21,10 +37,8 @@ interface DynamicUiContract {
*/ */
fun reloadTextSize() fun reloadTextSize()
/** /**
* Change text size without propagation * Change text size without propagation
*/ */
fun reloadTextSizeSelf() 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 package com.pitchedapps.frost.contracts
import android.app.Activity import android.app.Activity
@ -17,12 +33,19 @@ import com.pitchedapps.frost.utils.L
const val MEDIA_CHOOSER_RESULT = 67 const val MEDIA_CHOOSER_RESULT = 67
interface FileChooserActivityContract { interface FileChooserActivityContract {
fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: WebChromeClient.FileChooserParams) fun openFileChooser(
filePathCallback: ValueCallback<Array<Uri>?>,
fileChooserParams: WebChromeClient.FileChooserParams
)
} }
interface FileChooserContract { interface FileChooserContract {
var filePathCallback: ValueCallback<Array<Uri>?>? 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 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 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, _ -> kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ ->
if (!granted) { if (!granted) {
filePathCallback.onReceiveValue(null) filePathCallback.onReceiveValue(null)
@ -52,5 +78,4 @@ class FileChooserDelegate : FileChooserContract {
filePathCallback = null filePathCallback = null
return true 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 package com.pitchedapps.frost.contracts
import android.view.View import android.view.View
@ -23,7 +39,6 @@ interface FrostContentContainer {
* Update toolbar title * Update toolbar title
*/ */
fun setTitle(title: String) fun setTitle(title: String)
} }
/** /**
@ -84,7 +99,6 @@ interface FrostContentParent : DynamicUiContract {
* For those cases, we will return false to stop it * For those cases, we will return false to stop it
*/ */
fun registerTransition(urlChanged: Boolean, animate: Boolean): Boolean fun registerTransition(urlChanged: Boolean, animate: Boolean): Boolean
} }
/** /**
@ -147,5 +161,4 @@ interface FrostContentCore : DynamicUiContract {
* Signal destruction to release some content manually * Signal destruction to release some content manually
*/ */
fun destroy() 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 package com.pitchedapps.frost.contracts
import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.BehaviorSubject
@ -27,5 +43,4 @@ interface FrostObservables {
other.progressObservable = progressObservable other.progressObservable = progressObservable
other.titleObservable = titleObservable 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 package com.pitchedapps.frost.contracts
import android.view.View import android.view.View
@ -18,12 +34,11 @@ interface FrostThemable {
fun reloadTheme() fun reloadTheme()
fun setTextColors(color: Int, vararg textViews: TextView?) = fun setTextColors(color: Int, vararg textViews: TextView?) =
themeViews(color, *textViews) { setTextColor(it) } themeViews(color, *textViews) { setTextColor(it) }
fun setBackgrounds(color: Int, vararg views: View?) = 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) = 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 package com.pitchedapps.frost.contracts
import android.app.Activity import android.app.Activity
@ -49,5 +65,4 @@ interface FrameWrapper {
setContentView(R.layout.activity_frame_wrapper) setContentView(R.layout.activity_frame_wrapper)
frameWrapper.inflate(layoutRes, true) 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 package com.pitchedapps.frost.dbflow
import android.os.Parcel 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.Database
import com.raizlabs.android.dbflow.annotation.PrimaryKey import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table 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 com.raizlabs.android.dbflow.structure.BaseModel
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -30,7 +52,8 @@ object CookiesDb {
@PaperParcel @PaperParcel
@Table(database = CookiesDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE) @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 { companion object {
@JvmField @JvmField
val CREATOR = PaperParcelCookieModel.CREATOR 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) 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(id: Long): CookieModel? =
fun loadFbCookie(name: String): CookieModel? = (select from CookieModel::class where (CookieModel_Table.name eq name)).querySingle() (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 * Loads cookies sorted by name
*/ */
fun loadFbCookiesAsync(callback: (cookies: List<CookieModel>) -> Unit) { 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) = {}) { inline fun saveFbCookie(cookie: CookieModel, crossinline callback: (() -> Unit) = {}) {
cookie.async save { cookie.async save {
@ -69,24 +96,24 @@ fun removeCookie(id: Long) {
} }
inline fun CookieModel.fetchUsername(crossinline callback: (String) -> Unit): Disposable = inline fun CookieModel.fetchUsername(crossinline callback: (String) -> Unit): Disposable =
ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { yes, _ -> ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { yes, _ ->
if (!yes) return@subscribe callback("") if (!yes) return@subscribe callback("")
var result = "" var result = ""
try { try {
result = frostJsoup(cookie, FbItem.PROFILE.url).title() result = frostJsoup(cookie, FbItem.PROFILE.url).title()
L.d { "Fetch username found" } L.d { "Fetch username found" }
} catch (e: Exception) { } catch (e: Exception) {
if (e !is UnknownHostException) if (e !is UnknownHostException)
e.logFrostEvent("Fetch username failed") e.logFrostEvent("Fetch username failed")
} finally { } finally {
if (result.isBlank() && (name?.isNotBlank() == true)) { if (result.isBlank() && (name?.isNotBlank() == true)) {
callback(name!!) callback(name!!)
return@subscribe return@subscribe
}
if (name != result) {
name = result
saveFbCookie(this@fetchUsername)
}
callback(result)
} }
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 package com.pitchedapps.frost.dbflow
import android.content.Context import android.content.Context
@ -14,11 +30,10 @@ object DbUtils {
fun db(name: String) = FlowManager.getDatabase(name) fun db(name: String) = FlowManager.getDatabase(name)
fun dbName(name: String) = "$name.db" fun dbName(name: String) = "$name.db"
fun deleteDatabase(c: Context, name: String) = c.deleteDatabase(dbName(name)) fun deleteDatabase(c: Context, name: String) = c.deleteDatabase(dbName(name))
} }
inline fun <reified T : Any> List<T>.replace(dbName: String) { inline fun <reified T : Any> List<T>.replace(dbName: String) {
L.d { "Replacing $dbName.db" } L.d { "Replacing $dbName.db" }
DbUtils.db(dbName).reset() DbUtils.db(dbName).reset()
FastStoreModelTransaction.saveBuilder(FlowManager.getModelAdapter(T::class.java)).addAll(this).build() 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 package com.pitchedapps.frost.dbflow
import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.FbItem
@ -39,4 +55,4 @@ fun loadFbTabs(): List<FbItem> {
fun List<FbItem>.save() { fun List<FbItem>.save() {
database<FbTabsDb>().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute() 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 package com.pitchedapps.frost.dbflow
import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.L
import com.raizlabs.android.dbflow.annotation.* import com.raizlabs.android.dbflow.annotation.ConflictAction
import com.raizlabs.android.dbflow.kotlinextensions.* 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.SQLiteType
import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration
import com.raizlabs.android.dbflow.structure.BaseModel import com.raizlabs.android.dbflow.structure.BaseModel
@ -18,7 +43,8 @@ object NotificationDb {
} }
@Migration(version = 2, database = NotificationDb::class) @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() { override fun onPreMigrate() {
super.onPreMigrate() super.onPreMigrate()
addColumn(SQLiteType.INTEGER, "epochIm") addColumn(SQLiteType.INTEGER, "epochIm")
@ -27,11 +53,14 @@ class NotificationMigration2(modelClass: Class<NotificationModel>) : AlterTableM
} }
@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE) @Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
data class NotificationModel(@PrimaryKey var id: Long = -1L, data class NotificationModel(
var epoch: Long = -1L, @PrimaryKey var id: Long = -1L,
var epochIm: Long = -1L) : BaseModel() 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) ?: NotificationModel(id = id)
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) { fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
@ -40,4 +69,4 @@ fun saveNotificationTime(notificationModel: NotificationModel, callback: (() ->
L._d { notificationModel } L._d { notificationModel }
callback?.invoke() 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 package com.pitchedapps.frost.debugger
import ca.allanwang.kau.logging.KauLoggerExtension 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> * Inspired by <a href="https://github.com/JonasCz/save-for-offline">Save for Offline</a>
*/ */
class OfflineWebsite(private val url: String, class OfflineWebsite(
private val cookie: String = "", private val url: String,
baseUrl: String? = null, private val cookie: String = "",
private val html: String? = null, baseUrl: String? = null,
/** private val html: String? = null,
* Directory that holds all the files /**
*/ * Directory that holds all the files
val baseDir: File, */
private val userAgent: String = USER_AGENT_BASIC) { val baseDir: File,
private val userAgent: String = USER_AGENT_BASIC
) {
/** /**
* Supplied url without the queries * Supplied url without the queries
*/ */
private val baseUrl = (baseUrl ?: url.substringBefore("?") private val baseUrl = (baseUrl ?: url.substringBefore("?")
.substringBefore(".com")).trim('/') .substringBefore(".com")).trim('/')
private val mainFile = File(baseDir, "index.html") private val mainFile = File(baseDir, "index.html")
private val assetDir = File(baseDir, "assets") private val assetDir = File(baseDir, "assets")
@ -67,11 +85,11 @@ class OfflineWebsite(private val url: String,
private val cssQueue = mutableSetOf<String>() private val cssQueue = mutableSetOf<String>()
private fun request(url: String) = Request.Builder() private fun request(url: String) = Request.Builder()
.header("Cookie", cookie) .header("Cookie", cookie)
.header("User-Agent", userAgent) .header("User-Agent", userAgent)
.url(url) .url(url)
.get() .get()
.call() .call()
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
@ -94,7 +112,6 @@ class OfflineWebsite(private val url: String,
return callback(false) return callback(false)
} }
if (!assetDir.createFreshDir()) { if (!assetDir.createFreshDir()) {
L.e { "Could not create ${assetDir.absolutePath}" } L.e { "Could not create ${assetDir.absolutePath}" }
return callback(false) return callback(false)
@ -245,8 +262,10 @@ class OfflineWebsite(private val url: String,
} }
}) })
private inline fun <T> String.downloadUrl(fallback: () -> T, private inline fun <T> String.downloadUrl(
action: (file: File, body: ResponseBody) -> T): T { fallback: () -> T,
action: (file: File, body: ResponseBody) -> T
): T {
val file = File(assetDir, fileName()) val file = File(assetDir, fileName())
if (!file.createNewFile()) { if (!file.createNewFile()) {
@ -289,11 +308,10 @@ class OfflineWebsite(private val url: String,
if (mapped != null) return mapped if (mapped != null) return mapped
val candidate = substringBefore("?").trim('/') val candidate = substringBefore("?").trim('/')
.substringAfterLast("/").shorten() .substringAfterLast("/").shorten()
val index = atomicInt.getAndIncrement() val index = atomicInt.getAndIncrement()
var newUrl = "a${index}_$candidate" var newUrl = "a${index}_$candidate"
/** /**
@ -308,10 +326,10 @@ class OfflineWebsite(private val url: String,
} }
private fun String.shorten() = 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> = private fun Set<String>.clean(): List<String> =
filter(String::isNotBlank).filter { it.startsWith("http") } filter(String::isNotBlank).filter { it.startsWith("http") }
private fun reset() { private fun reset() {
cancelled = false cancelled = false
@ -326,5 +344,4 @@ class OfflineWebsite(private val url: String,
compositeDisposable.dispose() compositeDisposable.dispose()
L.v { "Request cancelled" } 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 package com.pitchedapps.frost.enums
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -16,4 +32,4 @@ enum class FeedSort(@StringRes val textRes: Int, val item: FbItem) {
val values = values() //save one instance val values = values() //save one instance
operator fun invoke(index: Int) = values[index] 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 package com.pitchedapps.frost.enums
import com.pitchedapps.frost.R import com.pitchedapps.frost.R
@ -7,23 +23,24 @@ import com.pitchedapps.frost.utils.Prefs
* Created by Allan Wang on 2017-08-19. * Created by Allan Wang on 2017-08-19.
*/ */
enum class MainActivityLayout( enum class MainActivityLayout(
val titleRes: Int, val titleRes: Int,
val layoutRes: Int, val layoutRes: Int,
val backgroundColor: () -> Int, val backgroundColor: () -> Int,
val iconColor: () -> Int) { val iconColor: () -> Int
) {
TOP_BAR(R.string.top_bar, TOP_BAR(R.string.top_bar,
R.layout.activity_main, R.layout.activity_main,
{ Prefs.headerColor }, { Prefs.headerColor },
{ Prefs.iconColor }), { Prefs.iconColor }),
BOTTOM_BAR(R.string.bottom_bar, BOTTOM_BAR(R.string.bottom_bar,
R.layout.activity_main_bottom_tabs, R.layout.activity_main_bottom_tabs,
{ Prefs.bgColor }, { Prefs.bgColor },
{ Prefs.textColor }); { Prefs.textColor });
companion object { companion object {
val values = values() //save one instance val values = values() //save one instance
operator fun invoke(index: Int) = values[index] 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 package com.pitchedapps.frost.enums
import android.content.Context import android.content.Context
@ -52,12 +68,13 @@ enum class OverlayContext(private val menuItem: FrostMenuItem?) : EnumBundle<Ove
* Frame for an injectable menu item * Frame for an injectable menu item
*/ */
class FrostMenuItem( class FrostMenuItem(
val id: Int, val id: Int,
val fbItem: FbItem, val fbItem: FbItem,
val showAsAction: Int = MenuItem.SHOW_AS_ACTION_IF_ROOM) { val showAsAction: Int = MenuItem.SHOW_AS_ACTION_IF_ROOM
) {
fun addToMenu(context: Context, menu: Menu, index: Int) { fun addToMenu(context: Context, menu: Menu, index: Int) {
val item = menu.add(Menu.NONE, id, index, fbItem.titleId) val item = menu.add(Menu.NONE, id, index, fbItem.titleId)
item.icon = fbItem.icon.toDrawable(context, 18) item.icon = fbItem.icon.toDrawable(context, 18)
item.setShowAsAction(showAsAction) 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 package com.pitchedapps.frost.enums
import android.content.Context 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 package com.pitchedapps.frost.enums
import android.graphics.Color import android.graphics.Color
@ -14,61 +30,63 @@ import com.pitchedapps.frost.utils.Prefs
const val FACEBOOK_BLUE = 0xff3b5998.toInt() const val FACEBOOK_BLUE = 0xff3b5998.toInt()
const val BLUE_LIGHT = 0xff5d86dd.toInt() const val BLUE_LIGHT = 0xff5d86dd.toInt()
enum class Theme(@StringRes val textRes: Int, enum class Theme(
val injector: InjectorContract, @StringRes val textRes: Int,
private val textColorGetter: () -> Int, val injector: InjectorContract,
private val accentColorGetter: () -> Int, private val textColorGetter: () -> Int,
private val backgroundColorGetter: () -> Int, private val accentColorGetter: () -> Int,
private val headerColorGetter: () -> Int, private val backgroundColorGetter: () -> Int,
private val iconColorGetter: () -> Int) { private val headerColorGetter: () -> Int,
private val iconColorGetter: () -> Int
) {
DEFAULT(R.string.kau_default, DEFAULT(R.string.kau_default,
JsActions.EMPTY, JsActions.EMPTY,
{ 0xde000000.toInt() }, { 0xde000000.toInt() },
{ FACEBOOK_BLUE }, { FACEBOOK_BLUE },
{ 0xfffafafa.toInt() }, { 0xfffafafa.toInt() },
{ FACEBOOK_BLUE }, { FACEBOOK_BLUE },
{ Color.WHITE }), { Color.WHITE }),
LIGHT(R.string.kau_light, LIGHT(R.string.kau_light,
CssAssets.MATERIAL_LIGHT, CssAssets.MATERIAL_LIGHT,
{ 0xde000000.toInt() }, { 0xde000000.toInt() },
{ FACEBOOK_BLUE }, { FACEBOOK_BLUE },
{ 0xfffafafa.toInt() }, { 0xfffafafa.toInt() },
{ FACEBOOK_BLUE }, { FACEBOOK_BLUE },
{ Color.WHITE }), { Color.WHITE }),
DARK(R.string.kau_dark, DARK(R.string.kau_dark,
CssAssets.MATERIAL_DARK, CssAssets.MATERIAL_DARK,
{ Color.WHITE }, { Color.WHITE },
{ BLUE_LIGHT }, { BLUE_LIGHT },
{ 0xff303030.toInt() }, { 0xff303030.toInt() },
{ 0xff2e4b86.toInt() }, { 0xff2e4b86.toInt() },
{ Color.WHITE }), { Color.WHITE }),
AMOLED(R.string.kau_amoled, AMOLED(R.string.kau_amoled,
CssAssets.MATERIAL_AMOLED, CssAssets.MATERIAL_AMOLED,
{ Color.WHITE }, { Color.WHITE },
{ BLUE_LIGHT }, { BLUE_LIGHT },
{ Color.BLACK }, { Color.BLACK },
{ Color.BLACK }, { Color.BLACK },
{ Color.WHITE }), { Color.WHITE }),
GLASS(R.string.kau_glass, GLASS(R.string.kau_glass,
CssAssets.MATERIAL_GLASS, CssAssets.MATERIAL_GLASS,
{ Color.WHITE }, { Color.WHITE },
{ BLUE_LIGHT }, { BLUE_LIGHT },
{ 0x80000000.toInt() }, { 0x80000000.toInt() },
{ 0xb3000000.toInt() }, { 0xb3000000.toInt() },
{ Color.WHITE }), { Color.WHITE }),
CUSTOM(R.string.kau_custom, CUSTOM(R.string.kau_custom,
CssAssets.CUSTOM, CssAssets.CUSTOM,
{ Prefs.customTextColor }, { Prefs.customTextColor },
{ Prefs.customAccentColor }, { Prefs.customAccentColor },
{ Prefs.customBackgroundColor }, { Prefs.customBackgroundColor },
{ Prefs.customHeaderColor }, { Prefs.customHeaderColor },
{ Prefs.customIconColor }); { Prefs.customIconColor });
val textColor: Int val textColor: Int
get() = textColorGetter() get() = textColorGetter()
@ -89,4 +107,4 @@ enum class Theme(@StringRes val textRes: Int,
val values = values() //save one instance val values = values() //save one instance
operator fun invoke(index: Int) = values[index] 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 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_LOGIN_URL = "${FB_URL_BASE}login"
const val FB_HOME_URL = "${FB_URL_BASE}home.php" 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_FULL =
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+" "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_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_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 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 * Note that transitions are also called from onFinish, so this value
* will never make a load slower than it is * 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 package com.pitchedapps.frost.facebook
import android.app.Activity import android.app.Activity
@ -38,13 +54,13 @@ object FbCookie {
val cookies = cookie.split(";").map { Pair(it, SingleSubject.create<Boolean>()) } val cookies = cookie.split(";").map { Pair(it, SingleSubject.create<Boolean>()) }
cookies.forEach { (cookie, callback) -> setCookie(FB_URL_BASE, cookie) { callback.onSuccess(it) } } cookies.forEach { (cookie, callback) -> setCookie(FB_URL_BASE, cookie) { callback.onSuccess(it) } }
Observable.zip<Boolean, Unit>(cookies.map { (_, callback) -> callback.toObservable() }) {} Observable.zip<Boolean, Unit>(cookies.map { (_, callback) -> callback.toObservable() }) {}
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
callback?.invoke() callback?.invoke()
L.d { "Cookies set" } L.d { "Cookies set" }
L._d { cookie } L._d { cookie }
flush() flush()
} }
} }
} }
} }
@ -132,4 +148,4 @@ object FbCookie {
} }
} else callback() } 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 package com.pitchedapps.frost.facebook
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -15,10 +31,10 @@ import com.pitchedapps.frost.utils.EnumBundleCompanion
import com.pitchedapps.frost.utils.EnumCompanion import com.pitchedapps.frost.utils.EnumCompanion
enum class FbItem( enum class FbItem(
@StringRes val titleId: Int, @StringRes val titleId: Int,
val icon: IIcon, val icon: IIcon,
relativeUrl: String, relativeUrl: String,
val fragmentCreator: () -> BaseFragment = ::WebFragment val fragmentCreator: () -> BaseFragment = ::WebFragment
) : EnumBundle<FbItem> { ) : EnumBundle<FbItem> {
ACTIVITY_LOG(R.string.activity_log, GoogleMaterial.Icon.gmd_list, "me/allactivity"), 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 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.*?)\"") val FB_REDIRECT_URL_MATCHER: Regex = Regex("url=(.*?fbcdn.*?)\"")
operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex) 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 package com.pitchedapps.frost.facebook
import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.L
@ -91,13 +107,13 @@ class FbUrlFormatter(url: String) {
* That shouldn't break anything * That shouldn't break anything
*/ */
val discardable = arrayOf( val discardable = arrayOf(
"http://lm.facebook.com/l.php?u=", "http://lm.facebook.com/l.php?u=",
"https://lm.facebook.com/l.php?u=", "https://lm.facebook.com/l.php?u=",
"http://m.facebook.com/l.php?u=", "http://m.facebook.com/l.php?u=",
"https://m.facebook.com/l.php?u=", "https://m.facebook.com/l.php?u=",
"http://touch.facebook.com/l.php?u=", "http://touch.facebook.com/l.php?u=",
"https://touch.facebook.com/l.php?u=", "https://touch.facebook.com/l.php?u=",
VIDEO_REDIRECT VIDEO_REDIRECT
) )
/** /**
@ -108,13 +124,13 @@ class FbUrlFormatter(url: String) {
val discardableQueries = arrayOf("ref", "refid", "SharedWith") val discardableQueries = arrayOf("ref", "refid", "SharedWith")
val converter = listOf( val converter = listOf(
"\\3C " to "%3C", "\\3E " to "%3E", "\\23 " to "%23", "\\25 " to "%25", "\\3C " to "%3C", "\\3E " to "%3E", "\\23 " to "%23", "\\25 " to "%25",
"\\7B " to "%7B", "\\7D " to "%7D", "\\7C " to "%7C", "\\5C " to "%5C", "\\7B " to "%7B", "\\7D " to "%7D", "\\7C " to "%7C", "\\5C " to "%5C",
"\\5E " to "%5E", "\\7E " to "%7E", "\\5B " to "%5B", "\\5D " to "%5D", "\\5E " to "%5E", "\\7E " to "%7E", "\\5B " to "%5B", "\\5D " to "%5D",
"\\60 " to "%60", "\\3B " to "%3B", "\\2F " to "%2F", "\\3F " to "%3F", "\\60 " to "%60", "\\3B " to "%3B", "\\2F " to "%2F", "\\3F " to "%3F",
"\\3A " to "%3A", "\\40 " to "%40", "\\3D " to "%3D", "\\26 " to "%26", "\\3A " to "%3A", "\\40 " to "%40", "\\3D " to "%3D", "\\26 " to "%26",
"\\24 " to "%24", "\\2B " to "%2B", "\\22 " to "%22", "\\2C " to "%2C", "\\24 " to "%24", "\\2B " to "%2B", "\\22 " to "%22", "\\2C " to "%2C",
"\\20 " to "%20" "\\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 package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.CookieModel
@ -54,7 +70,6 @@ interface FrostParser<out T : Any> {
* Call parsing with given data * Call parsing with given data
*/ */
fun parseFromData(cookie: String?, text: String): ParseResponse<T>? fun parseFromData(cookie: String?, text: String): ParseResponse<T>?
} }
const val FALLBACK_TIME_MOD = 1000000 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>? = 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>? { override fun parse(cookie: String?, document: Document): ParseResponse<T>? {
cookie ?: return null 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 * Returns the formatted url, or an empty string if nothing was found
*/ */
protected fun Element.getInnerImgStyle(): String? = protected fun Element.getInnerImgStyle(): String? =
select("i.img[style*=url]").getStyleUrl() select("i.img[style*=url]").getStyleUrl()
protected fun Elements.getStyleUrl(): String? = 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? = protected open fun textToDoc(text: String): Document? =
if (!redirectToText) Jsoup.parse(text) if (!redirectToText) Jsoup.parse(text)
else throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc") else throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc")
protected fun parseLink(element: Element?): FrostLink? { protected fun parseLink(element: Element?): FrostLink? {
val a = element?.getElementsByTag("a")?.first() ?: return null val a = element?.getElementsByTag("a")?.first() ?: return null
return FrostLink(a.text(), a.attr("href")) 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 package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.dbflow.CookieModel 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.services.NotificationContent
import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.L
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
@ -19,12 +39,12 @@ import org.jsoup.nodes.Element
object MessageParser : FrostParser<FrostMessages> by MessageParserImpl() { object MessageParser : FrostParser<FrostMessages> by MessageParserImpl() {
fun queryUser(cookie: String?, name: String) = parseFromUrl(cookie, "${FbItem.MESSAGES.url}/?q=$name") fun queryUser(cookie: String?, name: String) = parseFromUrl(cookie, "${FbItem.MESSAGES.url}/?q=$name")
} }
data class FrostMessages(val threads: List<FrostThread>, data class FrostMessages(
val seeMore: FrostLink?, val threads: List<FrostThread>,
val extraLinks: List<FrostLink> val seeMore: FrostLink?,
val extraLinks: List<FrostLink>
) : ParseNotification { ) : ParseNotification {
override fun toString() = StringBuilder().apply { override fun toString() = StringBuilder().apply {
append("FrostMessages {\n") append("FrostMessages {\n")
@ -35,19 +55,19 @@ data class FrostMessages(val threads: List<FrostThread>,
}.toString() }.toString()
override fun getUnreadNotifications(data: CookieModel) = override fun getUnreadNotifications(data: CookieModel) =
threads.asSequence().filter(FrostThread::unread).map { threads.asSequence().filter(FrostThread::unread).map {
with(it) { with(it) {
NotificationContent( NotificationContent(
data = data, data = data,
id = id, id = id,
href = url, href = url,
title = title, title = title,
text = content ?: "", text = content ?: "",
timestamp = time, timestamp = time,
profileUrl = img profileUrl = img
) )
} }
}.toList() }.toList()
} }
/** /**
@ -58,14 +78,16 @@ data class FrostMessages(val threads: List<FrostThread>,
* [unread] true if image is unread, false otherwise * [unread] true if image is unread, false otherwise
* [content] optional string for thread * [content] optional string for thread
*/ */
data class FrostThread(val id: Long, data class FrostThread(
val img: String?, val id: Long,
val title: String, val img: String?,
val time: Long, val title: String,
val url: String, val time: Long,
val unread: Boolean, val url: String,
val content: String?, val unread: Boolean,
val contentImgUrl: String?) val content: String?,
val contentImgUrl: String?
)
private class MessageParserImpl : FrostParserBase<FrostMessages>(true) { private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
@ -92,11 +114,12 @@ private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
override fun parseImpl(doc: Document): FrostMessages? { override fun parseImpl(doc: Document): FrostMessages? {
val threadList = doc.getElementById("threadlist_rows") ?: return null 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) .mapNotNull(this::parseMessage)
val seeMore = parseLink(doc.getElementById("see_older_threads")) val seeMore = parseLink(doc.getElementById("see_older_threads"))
val extraLinks = threadList.nextElementSibling().select("a") val extraLinks = threadList.nextElementSibling().select("a")
.mapNotNull(this::parseLink) .mapNotNull(this::parseLink)
return FrostMessages(threads, seeMore, extraLinks) 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 val epoch = FB_EPOCH_MATCHER.find(abbr.attr("data-store"))[1]?.toLongOrNull() ?: -1L
//fetch id //fetch id
val id = FB_MESSAGE_NOTIF_ID_MATCHER.find(element.id())[1]?.toLongOrNull() 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 snippet = element.select("span.snippet").firstOrNull()
val content = snippet?.text()?.trim() val content = snippet?.text()?.trim()
val contentImg = snippet?.select("i[style*=url]")?.getStyleUrl() val contentImg = snippet?.select("i[style*=url]")?.getStyleUrl()
val img = element.getInnerImgStyle() val img = element.getInnerImgStyle()
return FrostThread( return FrostThread(
id = id, id = id,
img = img, img = img,
title = a.text(), title = a.text(),
time = epoch, time = epoch,
url = a.attr("href").formattedFbUrl, url = a.attr("href").formattedFbUrl,
unread = !element.hasClass("acw"), unread = !element.hasClass("acw"),
content = content, content = content,
contentImgUrl = contentImg 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 package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.dbflow.CookieModel 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 com.pitchedapps.frost.services.NotificationContent
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -13,8 +33,8 @@ import org.jsoup.nodes.Element
object NotifParser : FrostParser<FrostNotifs> by NotifParserImpl() object NotifParser : FrostParser<FrostNotifs> by NotifParserImpl()
data class FrostNotifs( data class FrostNotifs(
val notifs: List<FrostNotif>, val notifs: List<FrostNotif>,
val seeMore: FrostLink? val seeMore: FrostLink?
) : ParseNotification { ) : ParseNotification {
override fun toString() = StringBuilder().apply { override fun toString() = StringBuilder().apply {
append("FrostNotifs {\n") append("FrostNotifs {\n")
@ -24,19 +44,19 @@ data class FrostNotifs(
}.toString() }.toString()
override fun getUnreadNotifications(data: CookieModel) = override fun getUnreadNotifications(data: CookieModel) =
notifs.asSequence().filter(FrostNotif::unread).map { notifs.asSequence().filter(FrostNotif::unread).map {
with(it) { with(it) {
NotificationContent( NotificationContent(
data = data, data = data,
id = id, id = id,
href = url, href = url,
title = null, title = null,
text = content, text = content,
timestamp = time, timestamp = time,
profileUrl = img profileUrl = img
) )
} }
}.toList() }.toList()
} }
/** /**
@ -49,14 +69,16 @@ data class FrostNotifs(
* [timeString] text version of time from Facebook * [timeString] text version of time from Facebook
* [thumbnailUrl] optional thumbnail url if existent * [thumbnailUrl] optional thumbnail url if existent
*/ */
data class FrostNotif(val id: Long, data class FrostNotif(
val img: String?, val id: Long,
val time: Long, val img: String?,
val url: String, val time: Long,
val unread: Boolean, val url: String,
val content: String, val unread: Boolean,
val timeString: String, val content: String,
val thumbnailUrl: String?) val timeString: String,
val thumbnailUrl: String?
)
private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) { private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
@ -67,8 +89,8 @@ private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
override fun parseImpl(doc: Document): FrostNotifs? { override fun parseImpl(doc: Document): FrostNotifs? {
val notificationList = doc.getElementById("notifications_list") ?: return null val notificationList = doc.getElementById("notifications_list") ?: return null
val notifications = notificationList val notifications = notificationList
.getElementsByAttributeValueMatching("id", ".*${FB_NOTIF_ID_MATCHER.pattern}.*") .getElementsByAttributeValueMatching("id", ".*${FB_NOTIF_ID_MATCHER.pattern}.*")
.mapNotNull(this::parseNotif) .mapNotNull(this::parseNotif)
val seeMore = parseLink(doc.getElementsByAttributeValue("href", "/notifications.php?more").first()) val seeMore = parseLink(doc.getElementsByAttributeValue("href", "/notifications.php?more").first())
return FrostNotifs(notifications, seeMore) 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 val epoch = FB_EPOCH_MATCHER.find(abbr.attr("data-store"))[1]?.toLongOrNull() ?: -1L
//fetch id //fetch id
val id = FB_NOTIF_ID_MATCHER.find(element.id())[1]?.toLongOrNull() 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 img = element.getInnerImgStyle()
val timeString = abbr.text() val timeString = abbr.text()
val content = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove &nbsp; val content = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove &nbsp;
val thumbnail = element.selectFirst("img.thumbnail")?.attr("src") val thumbnail = element.selectFirst("img.thumbnail")?.attr("src")
return FrostNotif( return FrostNotif(
id = id, id = id,
img = img, img = img,
time = epoch, time = epoch,
url = a.attr("href").formattedFbUrl, url = a.attr("href").formattedFbUrl,
unread = !element.hasClass("acw"), unread = !element.hasClass("acw"),
content = content, content = content,
timeString = timeString, timeString = timeString,
thumbnailUrl = if (thumbnail?.isNotEmpty() == true) thumbnail else null 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 package com.pitchedapps.frost.facebook.parsers
import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.searchview.SearchItem
@ -46,9 +62,9 @@ data class FrostSearch(val href: String, val title: String, val description: Str
companion object { companion object {
fun create(href: String, title: String, description: String?) = FrostSearch( fun create(href: String, title: String, description: String?) = FrostSearch(
with(href.indexOf("?")) { if (this == -1) href else href.substring(0, this) }, with(href.indexOf("?")) { if (this == -1) href else href.substring(0, this) },
title.format(), title.format(),
description?.format() description?.format()
) )
} }
} }
@ -61,17 +77,18 @@ private class SearchParserImpl : FrostParserBase<FrostSearches>(false) {
override fun parseImpl(doc: Document): FrostSearches? { override fun parseImpl(doc: Document): FrostSearches? {
val container: Element = doc.getElementById("BrowseResultsContainer") val container: Element = doc.getElementById("BrowseResultsContainer")
?: doc.getElementById("root") ?: doc.getElementById("root")
?: return null ?: return null
/** /**
* *
* Removed [data-store*=result_id] * Removed [data-store*=result_id]
*/ */
return FrostSearches(container.select("a.touchable[href]").filter(Element::hasText).map { return FrostSearches(container.select("a.touchable[href]").filter(Element::hasText).map {
FrostSearch.create(it.attr("href").formattedFbUrl, FrostSearch.create(
it.select("._uoi").first()?.text() ?: "", it.attr("href").formattedFbUrl,
it.select("._1tcc").first()?.text()) it.select("._uoi").first()?.text() ?: "",
it.select("._1tcc").first()?.text()
)
}.filter { it.title.isNotBlank() }) }.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 package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.BuildConfig 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.rx.RxFlyweight
import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.L
import io.reactivex.Single import io.reactivex.Single
@ -21,10 +43,9 @@ private class RxAuth : RxFlyweight<String, Long, RequestAuth>() {
override fun call(input: String) = input.getAuth() override fun call(input: String) = input.getAuth()
override fun validate(input: String, cond: Long) = 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() override fun cache(input: String) = System.currentTimeMillis()
} }
private val auth = RxAuth() private val auth = RxAuth()
@ -48,10 +69,12 @@ fun String?.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) {
/** /**
* Underlying container for all fb requests * Underlying container for all fb requests
*/ */
data class RequestAuth(val userId: Long = -1, data class RequestAuth(
val cookie: String = "", val userId: Long = -1,
val fb_dtsg: String = "", val cookie: String = "",
val rev: String = "") { val fb_dtsg: String = "",
val rev: String = ""
) {
val isComplete val isComplete
get() = userId > 0 && cookie.isNotEmpty() && fb_dtsg.isNotEmpty() && rev.isNotEmpty() 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( internal inline fun <T : Any?> RequestAuth.frostRequest(
noinline invoke: (Call) -> T, noinline invoke: (Call) -> T,
builder: Request.Builder.() -> Request.Builder // to ensure we don't do anything extra at the end builder: Request.Builder.() -> Request.Builder // to ensure we don't do anything extra at the end
): FrostRequest<T> { ): FrostRequest<T> {
val request = cookie.requestBuilder() val request = cookie.requestBuilder()
request.builder() request.builder()
@ -75,8 +98,10 @@ internal inline fun <T : Any?> RequestAuth.frostRequest(
val httpClient: OkHttpClient by lazy { val httpClient: OkHttpClient by lazy {
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
builder.addInterceptor(HttpLoggingInterceptor() builder.addInterceptor(
.setLevel(HttpLoggingInterceptor.Level.BASIC)) HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BASIC)
)
builder.build() builder.build()
} }
@ -97,7 +122,7 @@ internal fun List<Pair<String, Any?>>.withEmptyData(vararg key: String): List<Pa
internal fun String?.requestBuilder(): Request.Builder { internal fun String?.requestBuilder(): Request.Builder {
val builder = Request.Builder() val builder = Request.Builder()
.header("User-Agent", USER_AGENT_BASIC) .header("User-Agent", USER_AGENT_BASIC)
if (this != null) if (this != null)
builder.header("Cookie", this) builder.header("Cookie", this)
// .cacheControl(CacheControl.FORCE_NETWORK) // .cacheControl(CacheControl.FORCE_NETWORK)
@ -112,9 +137,9 @@ fun String.getAuth(): RequestAuth {
val id = FB_USER_MATCHER.find(this)[1]?.toLong() ?: return auth val id = FB_USER_MATCHER.find(this)[1]?.toLong() ?: return auth
auth = auth.copy(userId = id) auth = auth.copy(userId = id)
val call = this.requestBuilder() val call = this.requestBuilder()
.url(FB_URL_BASE) .url(FB_URL_BASE)
.get() .get()
.call() .call()
call.execute().body()?.charStream()?.useLines { lines -> call.execute().body()?.charStream()?.useLines { lines ->
lines.forEach { lines.forEach {
val text = StringEscapeUtils.unescapeEcmaScript(it) val text = StringEscapeUtils.unescapeEcmaScript(it)
@ -135,8 +160,10 @@ fun String.getAuth(): RequestAuth {
return auth return auth
} }
inline fun <T, reified R : Any, O> Array<T>.zip(crossinline mapper: (List<R>) -> O, inline fun <T, reified R : Any, O> Array<T>.zip(
crossinline caller: (T) -> R): Single<O> { crossinline mapper: (List<R>) -> O,
crossinline caller: (T) -> R
): Single<O> {
if (isEmpty()) if (isEmpty())
return Single.just(mapper(emptyList())) return Single.just(mapper(emptyList()))
val singles = map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) } 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 package com.pitchedapps.frost.facebook.requests
import com.bumptech.glide.Priority 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.RequestOptions
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.signature.ObjectKey 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 io.reactivex.Maybe
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
@ -33,9 +53,9 @@ val test: () -> InputStream? = { null }
*/ */
fun String.getFullSizedImageUrl(url: String): Maybe<String?> = Maybe.fromCallable { fun String.getFullSizedImageUrl(url: String): Maybe<String?> = Maybe.fromCallable {
val redirect = requestBuilder().url(url).get().call() 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 FB_REDIRECT_URL_MATCHER.find(redirect)[1]?.formattedFbUrl
?: return@fromCallable null ?: return@fromCallable null
}.onErrorComplete() }.onErrorComplete()
/** /**
@ -51,7 +71,6 @@ data class HdImageMaybe(val url: String, val cookie: String) {
val isValid: Boolean by lazy { val isValid: Boolean by lazy {
id != -1L && cookie.isNotBlank() id != -1L && cookie.isNotBlank()
} }
} }
/* /*
@ -69,18 +88,20 @@ class HdImageLoadingFactory : ModelLoaderFactory<HdImageMaybe, InputStream> {
} }
fun <T> RequestBuilder<T>.loadWithPotentialHd(model: HdImageMaybe) = fun <T> RequestBuilder<T>.loadWithPotentialHd(model: HdImageMaybe) =
thumbnail(clone().load(model.url)) thumbnail(clone().load(model.url))
.load(model) .load(model)
.apply(RequestOptions().override(Target.SIZE_ORIGINAL)) .apply(RequestOptions().override(Target.SIZE_ORIGINAL))
class HdImageLoading : ModelLoader<HdImageMaybe, InputStream> { class HdImageLoading : ModelLoader<HdImageMaybe, InputStream> {
override fun buildLoadData(model: HdImageMaybe, override fun buildLoadData(
width: Int, model: HdImageMaybe,
height: Int, width: Int,
options: Options): ModelLoader.LoadData<InputStream>? = height: Int,
if (!model.isValid) null options: Options
else ModelLoader.LoadData(ObjectKey(model), HdImageFetcher(model)) ): ModelLoader.LoadData<InputStream>? =
if (!model.isValid) null
else ModelLoader.LoadData(ObjectKey(model), HdImageFetcher(model))
override fun handles(model: HdImageMaybe) = model.isValid 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") }) { model.cookie.fbRequest(fail = { callback.fail("Invalid auth") }) {
if (cancelled) return@fbRequest callback.fail("Cancelled") if (cancelled) return@fbRequest callback.fail("Cancelled")
val url = getFullSizedImage(model.id).invoke() 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 (cancelled) return@fbRequest callback.fail("Cancelled")
if (!url.contains("png") && !url.contains("jpg")) return@fbRequest callback.fail("Invalid format") if (!url.contains("png") && !url.contains("jpg")) return@fbRequest callback.fail("Invalid format")
urlCall = Request.Builder().url(url).get().call() 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 package com.pitchedapps.frost.facebook.requests
import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonCreator
@ -19,28 +35,27 @@ import java.io.IOException
fun RequestAuth.getMenuData(): FrostRequest<MenuData?> { fun RequestAuth.getMenuData(): FrostRequest<MenuData?> {
val body = listOf( val body = listOf(
"fb_dtsg" to fb_dtsg, "fb_dtsg" to fb_dtsg,
"__user" to userId "__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::parseMenu) { return frostRequest(::parseMenu) {
url("${FB_URL_BASE}bookmarks/flyout/body/?id=u_0_2") url("${FB_URL_BASE}bookmarks/flyout/body/?id=u_0_2")
post(body.toForm()) post(body.toForm())
} }
} }
fun parseMenu(call: Call): MenuData? { fun parseMenu(call: Call): MenuData? {
val fullString = call.execute().body()?.string() ?: return null val fullString = call.execute().body()?.string() ?: return null
var jsonString = fullString.substringAfter("bookmarkGroups", "") var jsonString = fullString.substringAfter("bookmarkGroups", "")
.substringAfter("[", "") .substringAfter("[", "")
if (jsonString.isBlank()) return null if (jsonString.isBlank()) return null
jsonString = "{ \"data\" : [${StringEscapeUtils.unescapeEcmaScript(jsonString)}" jsonString = "{ \"data\" : [${StringEscapeUtils.unescapeEcmaScript(jsonString)}"
val mapper = ObjectMapper() val mapper = ObjectMapper()
.disable(MapperFeature.AUTO_DETECT_SETTERS) .disable(MapperFeature.AUTO_DETECT_SETTERS)
return try { return try {
val data = mapper.readValue(jsonString, MenuData::class.java) val data = mapper.readValue(jsonString, MenuData::class.java)
@ -48,11 +63,14 @@ fun parseMenu(call: Call): MenuData? {
// parse footer content // parse footer content
val footer = fullString.substringAfter("footerMarkup", "") val footer = fullString.substringAfter("footerMarkup", "")
.substringAfter("{", "") .substringAfter("{", "")
.substringBefore("}", "") .substringBefore("}", "")
val doc = Jsoup.parseBodyFragment(StringEscapeUtils.unescapeEcmaScript( val doc = Jsoup.parseBodyFragment(
StringEscapeUtils.unescapeEcmaScript(footer))) StringEscapeUtils.unescapeEcmaScript(
StringEscapeUtils.unescapeEcmaScript(footer)
)
)
val footerData = mutableListOf<MenuFooterItem>() val footerData = mutableListOf<MenuFooterItem>()
val footerSmallData = mutableListOf<MenuFooterItem>() val footerSmallData = mutableListOf<MenuFooterItem>()
@ -76,11 +94,14 @@ fun parseMenu(call: Call): MenuData? {
} }
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class MenuData(val data: List<MenuHeader> = emptyList(), data class MenuData(
val footer: MenuFooter = MenuFooter()) { val data: List<MenuHeader> = emptyList(),
val footer: MenuFooter = MenuFooter()
) {
@JsonCreator constructor( @JsonCreator
@JsonProperty("data") data: List<MenuHeader>? constructor(
@JsonProperty("data") data: List<MenuHeader>?
) : this(data ?: emptyList(), MenuFooter()) ) : this(data ?: emptyList(), MenuFooter())
fun flatMapValid(): List<MenuItemData> { fun flatMapValid(): List<MenuItemData> {
@ -95,7 +116,6 @@ data class MenuData(val data: List<MenuHeader> = emptyList(),
return items return items
} }
} }
interface MenuItemData { interface MenuItemData {
@ -103,17 +123,20 @@ interface MenuItemData {
} }
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class MenuHeader(val id: String? = null, data class MenuHeader(
val header: String? = null, val id: String? = null,
val visible: List<MenuItem> = emptyList(), val header: String? = null,
val all: List<MenuItem> = emptyList()) : MenuItemData { val visible: List<MenuItem> = emptyList(),
val all: List<MenuItem> = emptyList()
) : MenuItemData {
@JsonCreator constructor( @JsonCreator
@JsonProperty("id") id: String?, constructor(
@JsonProperty("header") header: String?, @JsonProperty("id") id: String?,
@JsonProperty("visible") visible: List<MenuItem>?, @JsonProperty("header") header: String?,
@JsonProperty("all") all: List<MenuItem>?, @JsonProperty("visible") visible: List<MenuItem>?,
@JsonProperty("fake") fake: Boolean? @JsonProperty("all") all: List<MenuItem>?,
@JsonProperty("fake") fake: Boolean?
) : this(id, header, visible ?: emptyList(), all ?: emptyList()) ) : this(id, header, visible ?: emptyList(), all ?: emptyList())
override val isValid: Boolean override val isValid: Boolean
@ -121,41 +144,49 @@ data class MenuHeader(val id: String? = null,
} }
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class MenuItem(val id: String? = null, data class MenuItem(
val name: String? = null, val id: String? = null,
val pic: String? = null, val name: String? = null,
val url: String? = null, val pic: String? = null,
val badge: String? = null, val url: String? = null,
val countDetails: String? = null) : MenuItemData { val badge: String? = null,
val countDetails: String? = null
) : MenuItemData {
@JsonCreator constructor( @JsonCreator
@JsonProperty("id") id: String?, constructor(
@JsonProperty("name") name: String?, @JsonProperty("id") id: String?,
@JsonProperty("pic") pic: String?, @JsonProperty("name") name: String?,
@JsonProperty("url") url: String?, @JsonProperty("pic") pic: String?,
@JsonProperty("count") badge: String?, @JsonProperty("url") url: String?,
@JsonProperty("count_details") countDetails: String?, @JsonProperty("count") badge: String?,
@JsonProperty("fake") fake: Boolean? @JsonProperty("count_details") countDetails: String?,
) : this(id, name, pic?.formattedFbUrl, @JsonProperty("fake") fake: Boolean?
url?.formattedFbUrl, ) : this(
if (badge == "0") null else badge, id, name, pic?.formattedFbUrl,
countDetails) url?.formattedFbUrl,
if (badge == "0") null else badge,
countDetails
)
override val isValid: Boolean override val isValid: Boolean
get() = !name.isNullOrBlank() && !url.isNullOrBlank() get() = !name.isNullOrBlank() && !url.isNullOrBlank()
} }
data class MenuFooter(val data: List<MenuFooterItem> = emptyList(), data class MenuFooter(
val smallData: List<MenuFooterItem> = emptyList()) { val data: List<MenuFooterItem> = emptyList(),
val smallData: List<MenuFooterItem> = emptyList()
) {
val hasContent val hasContent
get() = data.isNotEmpty() || smallData.isNotEmpty() get() = data.isNotEmpty() || smallData.isNotEmpty()
} }
data class MenuFooterItem(val name: String? = null, data class MenuFooterItem(
val url: String? = null, val name: String? = null,
val isSmall: Boolean = false) : MenuItemData { val url: String? = null,
val isSmall: Boolean = false
) : MenuItemData {
override val isValid: Boolean override val isValid: Boolean
get() = name != null && url != null 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 package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.facebook.FB_URL_BASE 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... // todo test more; only tested against tids=cid...
val body = listOf( val body = listOf(
"tids" to group, "tids" to group,
"body" to content, "body" to content,
"fb_dtsg" to fb_dtsg, "fb_dtsg" to fb_dtsg,
"__user" to userId "__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::validateMessage) { return frostRequest(::validateMessage) {
@ -29,4 +45,4 @@ private fun validateMessage(call: Call): Boolean {
val body = call.execute().body() ?: return false val body = call.execute().body() ?: return false
// todo // todo
return true 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 package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.facebook.FB_URL_BASE 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> { fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> {
val body = listOf( val body = listOf(
"click_type" to "notification_click", "click_type" to "notification_click",
"id" to notifId, "id" to notifId,
"target_id" to "null", "target_id" to "null",
"fb_dtsg" to fb_dtsg, "fb_dtsg" to fb_dtsg,
"__user" to userId "__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::executeForNoError) { return frostRequest(::executeForNoError) {
@ -22,6 +38,6 @@ fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> {
} }
fun RequestAuth.markNotificationsRead(vararg notifId: Long) = fun RequestAuth.markNotificationsRead(vararg notifId: Long) =
notifId.toTypedArray().zip<Long, Boolean, Boolean>( notifId.toTypedArray().zip<Long, Boolean, Boolean>(
{ it.all { self -> self } }, { it.all { self -> self } },
{ markNotificationRead(it).invoke() }) { 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 package com.pitchedapps.frost.fragments
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment
import ca.allanwang.kau.utils.fadeScaleTransition import ca.allanwang.kau.utils.fadeScaleTransition
import ca.allanwang.kau.utils.setIcon import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.withArguments import ca.allanwang.kau.utils.withArguments
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.contracts.DynamicUiContract import com.pitchedapps.frost.contracts.DynamicUiContract
import com.pitchedapps.frost.contracts.FrostContentParent 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.contracts.MainFabContract
import com.pitchedapps.frost.enums.FeedSort import com.pitchedapps.frost.enums.FeedSort
import com.pitchedapps.frost.facebook.FbItem 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.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable 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_POSITION = "arg_position"
private const val ARG_VALID = "arg_valid" 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 fragment = if (!useFallback) base() else WebFragment()
val d = if (data == FbItem.FEED) FeedSort(Prefs.feedSort).item else data val d = if (data == FbItem.FEED) FeedSort(Prefs.feedSort).item else data
fragment.withArguments( fragment.withArguments(
ARG_URL to d.url, ARG_URL to d.url,
ARG_POSITION to position ARG_POSITION to position
) )
d.put(fragment.arguments!!) d.put(fragment.arguments!!)
return fragment return fragment
@ -55,8 +81,10 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
if (value || this is WebFragment) return if (value || this is WebFragment) return
arguments!!.putBoolean(ARG_VALID, value) arguments!!.putBoolean(ARG_VALID, value)
L.e { "Invalidating position $position" } L.e { "Invalidating position $position" }
frostEvent("Native Fallback", frostEvent(
"Item" to baseEnum.name) "Native Fallback",
"Item" to baseEnum.name
)
(context as MainActivityContract).reloadFragment(this) (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") 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 view = inflater.inflate(layoutRes, container, false)
val content = view as? FrostContentParent val content = view as? FrostContentParent
?: throw IllegalArgumentException("layoutRes for fragment must return view implementing FrostContentParent") ?: throw IllegalArgumentException("layoutRes for fragment must return view implementing FrostContentParent")
this.content = content this.content = content
content.bind(this) content.bind(this)
return view return view
@ -113,28 +145,28 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
} }
override fun attachMainObservable(contract: MainActivityContract): Disposable = override fun attachMainObservable(contract: MainActivityContract): Disposable =
contract.fragmentSubject.observeOn(AndroidSchedulers.mainThread()).subscribe { contract.fragmentSubject.observeOn(AndroidSchedulers.mainThread()).subscribe {
when (it) { when (it) {
REQUEST_REFRESH -> { REQUEST_REFRESH -> {
core?.apply { core?.apply {
clearHistory() clearHistory()
firstLoad = true firstLoad = true
firstLoadRequest() firstLoadRequest()
}
}
position -> {
contract.setTitle(baseEnum.titleId)
updateFab(contract)
core?.active = true
}
-(position + 1) -> {
core?.active = false
}
REQUEST_TEXT_ZOOM -> {
reloadTextSize()
} }
} }
position -> {
contract.setTitle(baseEnum.titleId)
updateFab(contract)
core?.active = true
}
-(position + 1) -> {
core?.active = false
}
REQUEST_TEXT_ZOOM -> {
reloadTextSize()
}
} }
}
override fun updateFab(contract: MainFabContract) { override fun updateFab(contract: MainFabContract) {
contract.hideFab() // default contract.hideFab() // default
@ -197,4 +229,3 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
override fun onTabClick(): Unit = content?.core?.onTabClicked() ?: Unit 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 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 com.pitchedapps.frost.views.FrostRecyclerView
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -74,8 +94,6 @@ interface FragmentContract : FrostContentContainer {
fun onBackPressed(): Boolean fun onBackPressed(): Boolean
fun onTabClick() fun onTabClick()
} }
interface RecyclerContentContract { interface RecyclerContentContract {
@ -88,5 +106,4 @@ interface RecyclerContentContract {
* Callback returns [true] for success, [false] otherwise * Callback returns [true] for success, [false] otherwise
*/ */
fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit) 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 package com.pitchedapps.frost.fragments
import ca.allanwang.kau.adapters.fastAdapter 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 * Create the fast adapter to bind to the recyclerview
*/ */
open fun getAdapter(): FastAdapter<IItem<*, *>> = fastAdapter(this.adapter) open fun getAdapter(): FastAdapter<IItem<*, *>> = fastAdapter(this.adapter)
} }
abstract class FrostParserFragment<T : Any, Item : IItem<*, *>> : RecyclerFragment() { 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 package com.pitchedapps.frost.fragments
import com.mikepenz.fastadapter.IItem 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.FrostNotifs
import com.pitchedapps.frost.facebook.parsers.NotifParser import com.pitchedapps.frost.facebook.parsers.NotifParser
import com.pitchedapps.frost.facebook.parsers.ParseResponse import com.pitchedapps.frost.facebook.parsers.ParseResponse
import com.pitchedapps.frost.facebook.requests.* import com.pitchedapps.frost.facebook.requests.MenuFooterItem
import com.pitchedapps.frost.iitems.* 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.utils.frostJsoup
import com.pitchedapps.frost.views.FrostRecyclerView import com.pitchedapps.frost.views.FrostRecyclerView
import org.jetbrains.anko.doAsync 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 getDoc(cookie: String?) = frostJsoup(cookie, "${FbItem.NOTIFICATIONS.url}?more")
override fun toItems(response: ParseResponse<FrostNotifs>): List<NotificationIItem> = 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) { override fun bindImpl(recyclerView: FrostRecyclerView) {
NotificationIItem.bindEvents(adapter) 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 package com.pitchedapps.frost.fragments
import android.webkit.WebView import android.webkit.WebView
@ -43,4 +59,4 @@ class WebFragment : BaseFragment() {
} }
super.updateFab(contract) 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 package com.pitchedapps.frost.glide
import android.content.Context import android.content.Context
@ -30,11 +46,11 @@ object FrostGlide {
} }
fun <T> RequestBuilder<T>.transform(vararg transformation: BitmapTransformation): RequestBuilder<T> = fun <T> RequestBuilder<T>.transform(vararg transformation: BitmapTransformation): RequestBuilder<T> =
when (transformation.size) { when (transformation.size) {
0 -> this 0 -> this
1 -> apply(RequestOptions.bitmapTransform(transformation[0])) 1 -> apply(RequestOptions.bitmapTransform(transformation[0]))
else -> apply(RequestOptions.bitmapTransform(MultiTransformation(*transformation))) else -> apply(RequestOptions.bitmapTransform(MultiTransformation(*transformation)))
} }
@GlideModule @GlideModule
class FrostGlideModule : AppGlideModule() { class FrostGlideModule : AppGlideModule() {
@ -48,7 +64,7 @@ class FrostGlideModule : AppGlideModule() {
} }
private fun getFrostHttpClient(): OkHttpClient = private fun getFrostHttpClient(): OkHttpClient =
OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build() OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build()
class FrostCookieInterceptor : Interceptor { class FrostCookieInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
@ -58,4 +74,4 @@ class FrostCookieInterceptor : Interceptor {
val request = origRequest.newBuilder().addHeader("Cookie", cookie).build() val request = origRequest.newBuilder().addHeader("Cookie", cookie).build()
return chain.proceed(request) 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 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.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import java.security.MessageDigest import java.security.MessageDigest
/** /**
* Created by Allan Wang on 27/12/17. * Created by Allan Wang on 27/12/17.
*/ */
@ -25,17 +45,17 @@ class RoundCornerTransformation : BitmapTransformation() {
bitmap.setHasAlpha(true) bitmap.setHasAlpha(true)
val radius = Math.min(width, height).toFloat() / val radius = Math.min(width, height).toFloat() /
(if (Prefs.showRoundedIcons) 2f else 10f) (if (Prefs.showRoundedIcons) 2f else 10f)
val canvas = Canvas(bitmap) val canvas = Canvas(bitmap)
val paint = Paint() val paint = Paint()
paint.isAntiAlias = true paint.isAntiAlias = true
paint.shader = BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) paint.shader = BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
canvas.drawRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), canvas.drawRoundRect(
radius, radius, paint) RectF(0f, 0f, width.toFloat(), height.toFloat()),
radius, radius, paint
)
return bitmap 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 package com.pitchedapps.frost.iitems
import android.content.Context import android.content.Context
@ -32,25 +48,25 @@ interface ClickableIItemContract {
companion object { companion object {
fun bindEvents(adapter: IAdapter<IItem<*, *>>) { fun bindEvents(adapter: IAdapter<IItem<*, *>>) {
adapter.fastAdapter.withSelectable(false) adapter.fastAdapter.withSelectable(false)
.withOnClickListener { v, _, item, _ -> .withOnClickListener { v, _, item, _ ->
if (item is ClickableIItemContract) { if (item is ClickableIItemContract) {
item.click(v!!.context) item.click(v!!.context)
true true
} else } else
false false
} }
} }
} }
} }
/** /**
* Generic header item * Generic header item
* Not clickable with an accent color * Not clickable with an accent color
*/ */
open class HeaderIItem(val text: String?, open class HeaderIItem(
itemId: Int = R.layout.iitem_header) val text: String?,
: KauIItem<HeaderIItem, HeaderIItem.ViewHolder>(R.layout.iitem_header, ::ViewHolder, itemId) { itemId: Int = R.layout.iitem_header
) : KauIItem<HeaderIItem, HeaderIItem.ViewHolder>(R.layout.iitem_header, ::ViewHolder, itemId) {
class ViewHolder(itemView: View) : FastAdapter.ViewHolder<HeaderIItem>(itemView) { class ViewHolder(itemView: View) : FastAdapter.ViewHolder<HeaderIItem>(itemView) {
@ -66,18 +82,18 @@ open class HeaderIItem(val text: String?,
text.text = null text.text = null
} }
} }
} }
/** /**
* Generic text item * Generic text item
* Clickable with text color * Clickable with text color
*/ */
open class TextIItem(val text: String?, open class TextIItem(
override val url: String?, val text: String?,
itemId: Int = R.layout.iitem_text) override val url: String?,
: KauIItem<TextIItem, TextIItem.ViewHolder>(R.layout.iitem_text, ::ViewHolder, itemId), itemId: Int = R.layout.iitem_text
ClickableIItemContract { ) : KauIItem<TextIItem, TextIItem.ViewHolder>(R.layout.iitem_text, ::ViewHolder, itemId),
ClickableIItemContract {
class ViewHolder(itemView: View) : FastAdapter.ViewHolder<TextIItem>(itemView) { class ViewHolder(itemView: View) : FastAdapter.ViewHolder<TextIItem>(itemView) {
@ -93,5 +109,4 @@ open class TextIItem(val text: String?,
text.text = null 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 package com.pitchedapps.frost.iitems
import android.view.View import android.view.View
@ -21,9 +37,9 @@ import com.pitchedapps.frost.utils.Prefs
/** /**
* Created by Allan Wang on 30/12/17. * Created by Allan Wang on 30/12/17.
*/ */
class MenuContentIItem(val data: MenuItem) class MenuContentIItem(val data: MenuItem) :
: KauIItem<MenuContentIItem, MenuContentIItem.ViewHolder>(R.layout.iitem_menu, ::ViewHolder), KauIItem<MenuContentIItem, MenuContentIItem.ViewHolder>(R.layout.iitem_menu, ::ViewHolder),
ClickableIItemContract { ClickableIItemContract {
override val url: String? override val url: String?
get() = data.url get() = data.url
@ -42,9 +58,9 @@ class MenuContentIItem(val data: MenuItem)
val iconUrl = item.data.pic val iconUrl = item.data.pic
if (iconUrl != null) if (iconUrl != null)
GlideApp.with(itemView) GlideApp.with(itemView)
.load(iconUrl) .load(iconUrl)
.transform(FrostGlide.roundCorner) .transform(FrostGlide.roundCorner)
.into(icon.visible()) .into(icon.visible())
else else
icon.gone() icon.gone()
content.text = item.data.name content.text = item.data.name
@ -59,12 +75,11 @@ class MenuContentIItem(val data: MenuItem)
} }
} }
class MenuHeaderIItem(val data: MenuHeader) : HeaderIItem(data.header, class MenuHeaderIItem(val data: MenuHeader) : HeaderIItem(
itemId = R.id.item_menu_header) data.header,
itemId = R.id.item_menu_header
)
class MenuFooterIItem(val data: MenuFooterItem) class MenuFooterIItem(val data: MenuFooterItem) : TextIItem(data.name, data.url, R.id.item_menu_footer)
: 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 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 package com.pitchedapps.frost.iitems
import android.view.View import android.view.View
@ -26,23 +42,24 @@ import com.pitchedapps.frost.utils.launchWebOverlay
/** /**
* Created by Allan Wang on 27/12/17. * 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 R.layout.iitem_notification, ::ViewHolder
) { ) {
companion object { companion object {
fun bindEvents(adapter: ItemAdapter<NotificationIItem>) { fun bindEvents(adapter: ItemAdapter<NotificationIItem>) {
adapter.fastAdapter.withSelectable(false) adapter.fastAdapter.withSelectable(false)
.withOnClickListener { v, _, item, position -> .withOnClickListener { v, _, item, position ->
val notif = item.notification val notif = item.notification
if (notif.unread) { if (notif.unread) {
FrostRunnable.markNotificationRead(v!!.context, notif.id, item.cookie) FrostRunnable.markNotificationRead(v!!.context, notif.id, item.cookie)
adapter.set(position, NotificationIItem(notif.copy(unread = false), 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 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 //todo see if necessary
@ -52,12 +69,17 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
private class Diff : DiffCallback<NotificationIItem> { private class Diff : DiffCallback<NotificationIItem> {
override fun areItemsTheSame(oldItem: NotificationIItem, newItem: 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) = 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 return newItem
} }
} }
@ -75,15 +97,17 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
override fun bindView(item: NotificationIItem, payloads: MutableList<Any>) { override fun bindView(item: NotificationIItem, payloads: MutableList<Any>) {
val notif = item.notification val notif = item.notification
frame.background = createSimpleRippleDrawable(Prefs.textColor, frame.background = createSimpleRippleDrawable(
Prefs.nativeBgColor(notif.unread)) Prefs.textColor,
Prefs.nativeBgColor(notif.unread)
)
content.setTextColor(Prefs.textColor) content.setTextColor(Prefs.textColor)
date.setTextColor(Prefs.textColor.withAlpha(150)) date.setTextColor(Prefs.textColor.withAlpha(150))
val glide = glide val glide = glide
glide.load(notif.img) glide.load(notif.img)
.transform(FrostGlide.roundCorner) .transform(FrostGlide.roundCorner)
.into(avatar) .into(avatar)
if (notif.thumbnailUrl != null) if (notif.thumbnailUrl != null)
glide.load(notif.thumbnailUrl).into(thumbnail.visible()) glide.load(notif.thumbnailUrl).into(thumbnail.visible())
@ -101,4 +125,4 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
date.text = null 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 package com.pitchedapps.frost.iitems
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import ca.allanwang.kau.iitems.KauIItem 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.FastAdapter
import com.mikepenz.fastadapter.IItem import com.mikepenz.fastadapter.IItem
import com.mikepenz.fastadapter_extensions.drag.IDraggable 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. * Created by Allan Wang on 26/11/17.
*/ */
class TabIItem(val item: FbItem) : KauIItem<TabIItem, TabIItem.ViewHolder>( class TabIItem(val item: FbItem) : KauIItem<TabIItem, TabIItem.ViewHolder>(
R.layout.iitem_tab_preview, R.layout.iitem_tab_preview,
{ ViewHolder(it) } { ViewHolder(it) }
), IDraggable<TabIItem, IItem<*, *>> { ), IDraggable<TabIItem, IItem<*, *>> {
override fun withIsDraggable(draggable: Boolean): TabIItem = this override fun withIsDraggable(draggable: Boolean): TabIItem = this
@ -45,6 +65,5 @@ class TabIItem(val item: FbItem) : KauIItem<TabIItem, TabIItem.ViewHolder>(
image.setImageDrawable(null) image.setImageDrawable(null)
text.visible().text = 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 package com.pitchedapps.frost.injectors
import android.graphics.Color import android.graphics.Color
import android.webkit.WebView import android.webkit.WebView
import ca.allanwang.kau.kotlin.lazyContext 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.L
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import java.io.BufferedReader import java.io.BufferedReader
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.* import java.util.Locale
/** /**
* Created by Allan Wang on 2017-05-31. * 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) val bb = Prefs.bgColor.colorToForeground(0.35f)
content = content content = content
.replace("\$T\$", Prefs.textColor.toRgbaString()) .replace("\$T\$", Prefs.textColor.toRgbaString())
.replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString()) .replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString())
.replace("\$A\$", Prefs.accentColor.toRgbaString()) .replace("\$A\$", Prefs.accentColor.toRgbaString())
.replace("\$B\$", Prefs.bgColor.toRgbaString()) .replace("\$B\$", Prefs.bgColor.toRgbaString())
.replace("\$BT\$", bt) .replace("\$BT\$", bt)
.replace("\$BBT\$", bb.withAlpha(51).toRgbaString()) .replace("\$BBT\$", bb.withAlpha(51).toRgbaString())
.replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString()) .replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
.replace("\$OO\$", bb.withAlpha(255).toRgbaString()) .replace("\$OO\$", bb.withAlpha(255).toRgbaString())
.replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString()) .replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
.replace("\$TI\$", bb.withAlpha(60).toRgbaString()) .replace("\$TI\$", bb.withAlpha(60).toRgbaString())
.replace("\$C\$", bt) .replace("\$C\$", bt)
} }
JsBuilder().css(content).build() JsBuilder().css(content).build()
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
@ -58,5 +79,4 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract {
fun reset() { fun reset() {
injector.invalidate() 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 package com.pitchedapps.frost.injectors
import android.webkit.WebView import android.webkit.WebView
@ -9,10 +25,14 @@ import android.webkit.WebView
*/ */
enum class CssHider(vararg val items: String) : InjectorContract { enum class CssHider(vararg val items: String) : InjectorContract {
CORE("[data-sigil=m_login_upsell]", "[role=progressbar]"), CORE("[data-sigil=m_login_upsell]", "[role=progressbar]"),
HEADER("#header", "#mJewelNav", "[data-sigil=MTopBlueBarHeader]", HEADER(
"#header-notices", "[data-sigil*=m-promo-jewel-header]"), "#header", "#mJewelNav", "[data-sigil=MTopBlueBarHeader]",
ADS("article[data-xt*=sponsor]", "#header-notices", "[data-sigil*=m-promo-jewel-header]"
"article[data-store*=sponsor]"), ),
ADS(
"article[data-xt*=sponsor]",
"article[data-store*=sponsor]"
),
PEOPLE_YOU_MAY_KNOW("article._d2r"), PEOPLE_YOU_MAY_KNOW("article._d2r"),
SUGGESTED_GROUPS("article[data-ft*=\"ei\":]"), SUGGESTED_GROUPS("article[data-ft*=\"ei\":]"),
COMPOSER("#MComposer"), COMPOSER("#MComposer"),
@ -22,11 +42,10 @@ enum class CssHider(vararg val items: String) : InjectorContract {
val injector: JsInjector by lazy { val injector: JsInjector by lazy {
JsBuilder().css("${items.joinToString(separator = ",")}{display:none !important}") JsBuilder().css("${items.joinToString(separator = ",")}{display:none !important}")
.single(name).build() .single(name).build()
} }
override fun inject(webView: WebView, callback: (() -> Unit)?) { override fun inject(webView: WebView, callback: (() -> Unit)?) {
injector.inject(webView, callback) 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 package com.pitchedapps.frost.injectors
import android.webkit.WebView import android.webkit.WebView
@ -27,10 +43,9 @@ enum class JsActions(body: String) : InjectorContract {
val function = "(function(){$body})();" val function = "(function(){$body})();"
override fun inject(webView: WebView, callback: (() -> Unit)?) = override fun inject(webView: WebView, callback: (() -> Unit)?) =
JsInjector(function).inject(webView, callback) JsInjector(function).inject(webView, callback)
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun clickBySelector(selector: String): String = 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 package com.pitchedapps.frost.injectors
import android.webkit.WebView import android.webkit.WebView
@ -5,7 +21,7 @@ import ca.allanwang.kau.kotlin.lazyContext
import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.L
import java.io.BufferedReader import java.io.BufferedReader
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.* import java.util.Locale
/** /**
* Created by Allan Wang on 2017-05-31. * Created by Allan Wang on 2017-05-31.
@ -31,5 +47,4 @@ enum class JsAssets : InjectorContract {
override fun inject(webView: WebView, callback: (() -> Unit)?) { override fun inject(webView: WebView, callback: (() -> Unit)?) {
injector(webView.context).inject(webView, callback) 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 package com.pitchedapps.frost.injectors
import android.webkit.WebView import android.webkit.WebView
@ -8,7 +24,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.subjects.SingleSubject import io.reactivex.subjects.SingleSubject
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
import java.util.* import java.util.Locale
class JsBuilder { class JsBuilder {
private val css = StringBuilder() 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 observables = Array(validInjectors.size) { SingleSubject.create<Unit>() }
val disposable = Single.zip<Unit, Int>(observables.asList()) { it.size } val disposable = Single.zip<Unit, Int>(observables.asList()) { it.size }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { res, _ -> .subscribe { res, _ ->
callback(res) callback(res)
} }
(0 until validInjectors.size).forEach { i -> (0 until validInjectors.size).forEach { i ->
validInjectors[i].inject(this) { validInjectors[i].inject(this) {
observables[i].onSuccess(Unit) observables[i].onSuccess(Unit)
@ -102,8 +118,10 @@ fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Int) -> Uni
return disposable return disposable
} }
fun FrostWebViewClient.jsInject(vararg injectors: InjectorContract, fun FrostWebViewClient.jsInject(
callback: ((Int) -> Unit)? = null) = web.jsInject(*injectors, callback = callback) vararg injectors: InjectorContract,
callback: ((Int) -> Unit)? = null
) = web.jsInject(*injectors, callback = callback)
/** /**
* Wrapper class to convert a function into an injector * 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)?) { override fun inject(webView: WebView, callback: (() -> Unit)?) {
webView.evaluateJavascript(function) { callback?.invoke() } 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 package com.pitchedapps.frost.intro
import android.os.Bundle import android.os.Bundle
@ -17,7 +33,11 @@ class IntroFragmentTheme : BaseIntroFragment(R.layout.intro_theme) {
val themeList val themeList
get() = listOf(intro_theme_light, intro_theme_dark, intro_theme_amoled, intro_theme_glass) 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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() } 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 package com.pitchedapps.frost.intro
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -5,7 +21,12 @@ import android.graphics.drawable.LayerDrawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ImageView 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.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
@ -15,9 +36,9 @@ import com.pitchedapps.frost.utils.launchTabCustomizerActivity
* Created by Allan Wang on 2017-07-28. * Created by Allan Wang on 2017-07-28.
*/ */
abstract class BaseImageIntroFragment( abstract class BaseImageIntroFragment(
val titleRes: Int, val titleRes: Int,
val imageRes: Int, val imageRes: Int,
val descRes: Int val descRes: Int
) : BaseIntroFragment(R.layout.intro_image) { ) : BaseIntroFragment(R.layout.intro_image) {
val imageDrawable: LayerDrawable by lazyResettableRegistered { image.drawable as LayerDrawable } val imageDrawable: LayerDrawable by lazyResettableRegistered { image.drawable as LayerDrawable }
@ -68,7 +89,7 @@ abstract class BaseImageIntroFragment(
} }
class IntroAccountFragment : 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() { override fun themeFragmentImpl() {
@ -85,7 +106,7 @@ class IntroAccountFragment : BaseImageIntroFragment(
} }
class IntroTabTouchFragment : 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -98,14 +119,20 @@ class IntroTabTouchFragment : BaseImageIntroFragment(
override fun themeFragmentImpl() { override fun themeFragmentImpl() {
super.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.headerColor, R.id.intro_phone_tab)
themeImageComponent(Prefs.textColor.withAlpha(80), R.id.intro_phone_icon_ripple) themeImageComponent(Prefs.textColor.withAlpha(80), R.id.intro_phone_icon_ripple)
} }
} }
class IntroTabContextFragment : BaseImageIntroFragment( 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() { 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.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.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.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) { override fun onPageScrolledImpl(positionOffset: Float) {
super.onPageScrolledImpl(positionOffset) super.onPageScrolledImpl(positionOffset)
lastImageFragmentTransition(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 package com.pitchedapps.frost.intro
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import ca.allanwang.kau.kotlin.LazyResettableRegistry import ca.allanwang.kau.kotlin.LazyResettableRegistry
import ca.allanwang.kau.utils.Kotterknife import ca.allanwang.kau.utils.Kotterknife
import ca.allanwang.kau.utils.bindViewResettable import ca.allanwang.kau.utils.bindViewResettable
@ -99,7 +115,6 @@ abstract class BaseIntroFragment(val layoutRes: Int) : Fragment() {
} }
protected open fun onPageSelectedImpl() { protected open fun onPageSelectedImpl() {
} }
} }
@ -131,4 +146,4 @@ class IntroFragmentEnd : BaseIntroFragment(R.layout.intro_end) {
(activity as IntroActivity).finish(event.x, event.y) (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 package com.pitchedapps.frost.rx
import io.reactivex.Single 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 * you likely won't have a need for flyweights
*/ */
protected open fun createNewSource(input: T): Single<R> = protected open fun createNewSource(input: T): Single<R> =
Single.fromCallable { call(input) } Single.fromCallable { call(input) }
.timeout(15, TimeUnit.SECONDS) .timeout(15, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
fun reset() { fun reset() {
synchronized(lock) { synchronized(lock) {
@ -82,5 +98,4 @@ abstract class RxFlyweight<in T : Any, C : Any, R : Any> {
conditionals.clear() 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 package com.pitchedapps.frost.services
import android.app.Notification 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.facebook.parsers.ParseNotification
import com.pitchedapps.frost.glide.FrostGlide import com.pitchedapps.frost.glide.FrostGlide
import com.pitchedapps.frost.glide.GlideApp import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.ARG_USER_ID
import java.util.* 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. * Created by Allan Wang on 2017-07-08.
@ -40,33 +60,38 @@ private val _40_DP = 40.dpToPx
* Enum to handle notification creations * Enum to handle notification creations
*/ */
enum class NotificationType( enum class NotificationType(
private val channelId: String, private val channelId: String,
private val overlayContext: OverlayContext, private val overlayContext: OverlayContext,
private val fbItem: FbItem, private val fbItem: FbItem,
private val parser: FrostParser<ParseNotification>, private val parser: FrostParser<ParseNotification>,
private val getTime: (notif: NotificationModel) -> Long, private val getTime: (notif: NotificationModel) -> Long,
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel, private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String) { private val ringtone: () -> String
) {
GENERAL(NOTIF_CHANNEL_GENERAL, GENERAL(
OverlayContext.NOTIFICATION, NOTIF_CHANNEL_GENERAL,
FbItem.NOTIFICATIONS, OverlayContext.NOTIFICATION,
NotifParser, FbItem.NOTIFICATIONS,
NotificationModel::epoch, NotifParser,
{ notif, time -> notif.copy(epoch = time) }, NotificationModel::epoch,
Prefs::notificationRingtone) { { notif, time -> notif.copy(epoch = time) },
Prefs::notificationRingtone
) {
override fun bindRequest(content: NotificationContent, cookie: String) = override fun bindRequest(content: NotificationContent, cookie: String) =
FrostRunnable.prepareMarkNotificationRead(content.id, cookie) FrostRunnable.prepareMarkNotificationRead(content.id, cookie)
}, },
MESSAGE(NOTIF_CHANNEL_MESSAGES, MESSAGE(
OverlayContext.MESSAGE, NOTIF_CHANNEL_MESSAGES,
FbItem.MESSAGES, OverlayContext.MESSAGE,
MessageParser, FbItem.MESSAGES,
NotificationModel::epochIm, MessageParser,
{ notif, time -> notif.copy(epochIm = time) }, NotificationModel::epochIm,
Prefs::messageRingtone); { notif, time -> notif.copy(epochIm = time) },
Prefs::messageRingtone
);
private val groupPrefix = "frost_${name.toLowerCase(Locale.CANADA)}" private val groupPrefix = "frost_${name.toLowerCase(Locale.CANADA)}"
@ -133,13 +158,15 @@ enum class NotificationType(
} }
fun debugNotification(context: Context, data: CookieModel) { fun debugNotification(context: Context, data: CookieModel) {
val content = NotificationContent(data, val content = NotificationContent(
System.currentTimeMillis(), data,
"https://github.com/AllanWang/Frost-for-Facebook", System.currentTimeMillis(),
"Debug Notif", "https://github.com/AllanWang/Frost-for-Facebook",
"Test 123", "Debug Notif",
System.currentTimeMillis() / 1000, "Test 123",
"https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png") System.currentTimeMillis() / 1000,
"https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png"
)
createNotification(context, content).notify(context) createNotification(context, content).notify(context)
} }
@ -147,44 +174,43 @@ enum class NotificationType(
* Create and submit a new notification with the given [content] * Create and submit a new notification with the given [content]
*/ */
private fun createNotification(context: Context, content: NotificationContent): FrostNotification = private fun createNotification(context: Context, content: NotificationContent): FrostNotification =
with(content) { with(content) {
val intent = Intent(context, FrostWebActivity::class.java) 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 // 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.data = Uri.parse(if (href.isIndependent) href else FbItem.NOTIFICATIONS.url)
intent.putExtra(ARG_USER_ID, data.id) intent.putExtra(ARG_USER_ID, data.id)
overlayContext.put(intent) overlayContext.put(intent)
bindRequest(intent, content, data.cookie) bindRequest(intent, content, data.cookie)
val group = "${groupPrefix}_${data.id}" val group = "${groupPrefix}_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notifBuilder = context.frostNotification(channelId) val notifBuilder = context.frostNotification(channelId)
.setContentTitle(title ?: context.string(R.string.frost_name)) .setContentTitle(title ?: context.string(R.string.frost_name))
.setContentText(text) .setContentText(text)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL) .setCategory(Notification.CATEGORY_SOCIAL)
.setSubText(data.name) .setSubText(data.name)
.setGroup(group) .setGroup(group)
if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
L.v { "Notif load $content" } L.v { "Notif load $content" }
if (profileUrl != null) { if (profileUrl != null) {
try { try {
val profileImg = GlideApp.with(context) val profileImg = GlideApp.with(context)
.asBitmap() .asBitmap()
.load(profileUrl) .load(profileUrl)
.transform(FrostGlide.circleCrop) .transform(FrostGlide.circleCrop)
.submit(_40_DP, _40_DP) .submit(_40_DP, _40_DP)
.get() .get()
notifBuilder.setLargeIcon(profileImg) notifBuilder.setLargeIcon(profileImg)
} catch (e: Exception) { } catch (e: Exception) {
L.e { "Failed to get image $profileUrl" } L.e { "Failed to get image $profileUrl" }
}
} }
FrostNotification(group, notifId, notifBuilder)
} }
FrostNotification(group, notifId, notifBuilder)
}
/** /**
* Create a summary notification to wrap the previous ones * Create a summary notification to wrap the previous ones
@ -198,12 +224,12 @@ enum class NotificationType(
val group = "${groupPrefix}_$userId" val group = "${groupPrefix}_$userId"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notifBuilder = context.frostNotification(channelId) val notifBuilder = context.frostNotification(channelId)
.setContentTitle(context.string(R.string.frost_name)) .setContentTitle(context.string(R.string.frost_name))
.setContentText("$count ${context.string(fbItem.titleId)}") .setContentText("$count ${context.string(fbItem.titleId)}")
.setGroup(group) .setGroup(group)
.setGroupSummary(true) .setGroupSummary(true)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL) .setCategory(Notification.CATEGORY_SOCIAL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notifBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) notifBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
@ -211,31 +237,33 @@ enum class NotificationType(
return FrostNotification(group, 1, notifBuilder) return FrostNotification(group, 1, notifBuilder)
} }
} }
/** /**
* Notification data holder * Notification data holder
*/ */
data class NotificationContent(val data: CookieModel, data class NotificationContent(
val id: Long, val data: CookieModel,
val href: String, val id: Long,
val title: String? = null, // defaults to frost title val href: String,
val text: String, val title: String? = null, // defaults to frost title
val timestamp: Long, val text: String,
val profileUrl: String?) { val timestamp: Long,
val profileUrl: String?
) {
val notifId = Math.abs(id.toInt()) val notifId = Math.abs(id.toInt())
} }
/** /**
* Wrapper for a complete notification builder and identifier * Wrapper for a complete notification builder and identifier
* which can be immediately notified when given a [Context] * which can be immediately notified when given a [Context]
*/ */
data class FrostNotification(private val tag: String, data class FrostNotification(
private val id: Int, private val tag: String,
val notif: NotificationCompat.Builder) { private val id: Int,
val notif: NotificationCompat.Builder
) {
fun withAlert(enable: Boolean, ringtone: String): FrostNotification { fun withAlert(enable: Boolean, ringtone: String): FrostNotification {
notif.setFrostAlert(enable, ringtone) notif.setFrostAlert(enable, ringtone)
@ -243,15 +271,15 @@ data class FrostNotification(private val tag: String,
} }
fun notify(context: Context) = 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 const val NOTIFICATION_PERIODIC_JOB = 7
fun Context.scheduleNotifications(minutes: Long): Boolean = fun Context.scheduleNotifications(minutes: Long): Boolean =
scheduleJob<NotificationService>(NOTIFICATION_PERIODIC_JOB, minutes) scheduleJob<NotificationService>(NOTIFICATION_PERIODIC_JOB, minutes)
const val NOTIFICATION_JOB_NOW = 6 const val NOTIFICATION_JOB_NOW = 6
fun Context.fetchNotifications(): Boolean = 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 package com.pitchedapps.frost.services
import android.app.job.JobInfo import android.app.job.JobInfo
@ -37,10 +53,10 @@ private enum class FrostRequestCommands : EnumBundle<FrostRequestCommands> {
} }
override fun propagate(bundle: BaseBundle) = override fun propagate(bundle: BaseBundle) =
FrostRunnable.prepareMarkNotificationRead( FrostRunnable.prepareMarkNotificationRead(
bundle.getLong(ARG_0), bundle.getLong(ARG_0),
bundle.getCookie()) bundle.getCookie()
)
}; };
override val bundleContract: EnumBundleCompanion<FrostRequestCommands> override val bundleContract: EnumBundleCompanion<FrostRequestCommands>
@ -58,7 +74,6 @@ private enum class FrostRequestCommands : EnumBundle<FrostRequestCommands> {
abstract fun propagate(bundle: BaseBundle): BaseBundle.() -> Unit abstract fun propagate(bundle: BaseBundle): BaseBundle.() -> Unit
companion object : EnumCompanion<FrostRequestCommands>("frost_arg_commands", values()) companion object : EnumCompanion<FrostRequestCommands>("frost_arg_commands", values())
} }
private const val ARG_COMMAND = "frost_request_command" private const val ARG_COMMAND = "frost_request_command"
@ -99,8 +114,10 @@ object FrostRunnable {
L.d { "Invalid notification id $id for marking as read" } L.d { "Invalid notification id $id for marking as read" }
return false return false
} }
return schedule(context, FrostRequestCommands.NOTIF_READ, return schedule(
prepareMarkNotificationRead(id, cookie)) context, FrostRequestCommands.NOTIF_READ,
prepareMarkNotificationRead(id, cookie)
)
} }
fun propagate(context: Context, intent: Intent?) { fun propagate(context: Context, intent: Intent?) {
@ -112,9 +129,11 @@ object FrostRunnable {
schedule(context, command, builder) schedule(context, command, builder)
} }
private fun schedule(context: Context, private fun schedule(
command: FrostRequestCommands, context: Context,
bundleBuilder: PersistableBundle.() -> Unit): Boolean { command: FrostRequestCommands,
bundleBuilder: PersistableBundle.() -> Unit
): Boolean {
val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(context, FrostRequestService::class.java) val serviceComponent = ComponentName(context, FrostRequestService::class.java)
val bundle = PersistableBundle() val bundle = PersistableBundle()
@ -127,10 +146,10 @@ object FrostRunnable {
} }
val builder = JobInfo.Builder(JOB_REQUEST_BASE + command.ordinal, serviceComponent) val builder = JobInfo.Builder(JOB_REQUEST_BASE + command.ordinal, serviceComponent)
.setMinimumLatency(0L) .setMinimumLatency(0L)
.setExtras(bundle) .setExtras(bundle)
.setOverrideDeadline(2000L) .setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
val result = scheduler.schedule(builder.build()) val result = scheduler.schedule(builder.build())
if (result <= 0) { if (result <= 0) {
L.eThrow("FrostRequestService scheduler failed for ${command.name}") L.eThrow("FrostRequestService scheduler failed for ${command.name}")
@ -139,7 +158,6 @@ object FrostRunnable {
L.d { "Scheduled ${command.name}" } L.d { "Scheduled ${command.name}" }
return true return true
} }
} }
class FrostRequestService : JobService() { class FrostRequestService : JobService() {
@ -183,4 +201,4 @@ class FrostRequestService : JobService() {
} }
return true 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 package com.pitchedapps.frost.services
import android.app.job.JobParameters import android.app.job.JobParameters
@ -31,10 +47,12 @@ class NotificationService : JobService() {
override fun onStopJob(params: JobParameters?): Boolean { override fun onStopJob(params: JobParameters?): Boolean {
val time = System.currentTimeMillis() - startTime val time = System.currentTimeMillis() - startTime
L.d { "Notification service has finished abruptly in $time ms" } L.d { "Notification service has finished abruptly in $time ms" }
frostEvent("NotificationTime", frostEvent(
"Type" to "Service force stop", "NotificationTime",
"IM Included" to Prefs.notificationsInstantMessages, "Type" to "Service force stop",
"Duration" to time) "IM Included" to Prefs.notificationsInstantMessages,
"Duration" to time
)
future?.cancel(true) future?.cancel(true)
future = null future = null
return false return false
@ -43,10 +61,12 @@ class NotificationService : JobService() {
fun finish(params: JobParameters?) { fun finish(params: JobParameters?) {
val time = System.currentTimeMillis() - startTime val time = System.currentTimeMillis() - startTime
L.i { "Notification service has finished in $time ms" } L.i { "Notification service has finished in $time ms" }
frostEvent("NotificationTime", frostEvent(
"Type" to "Service", "NotificationTime",
"IM Included" to Prefs.notificationsInstantMessages, "Type" to "Service",
"Duration" to time) "IM Included" to Prefs.notificationsInstantMessages,
"Duration" to time
)
jobFinished(params, false) jobFinished(params, false)
future?.cancel(true) future?.cancel(true)
future = null future = null
@ -61,11 +81,13 @@ class NotificationService : JobService() {
var notifCount = 0 var notifCount = 0
cookies.forEach { cookies.forEach {
val current = it.id == currentId val current = it.id == currentId
if (Prefs.notificationsGeneral if (Prefs.notificationsGeneral &&
&& (current || Prefs.notificationAllAccounts)) (current || Prefs.notificationAllAccounts)
)
notifCount += fetch(jobId, NotificationType.GENERAL, it) notifCount += fetch(jobId, NotificationType.GENERAL, it)
if (Prefs.notificationsInstantMessages if (Prefs.notificationsInstantMessages &&
&& (current || Prefs.notificationsImAllAccounts)) (current || Prefs.notificationsImAllAccounts)
)
notifCount += fetch(jobId, NotificationType.MESSAGE, it) notifCount += fetch(jobId, NotificationType.MESSAGE, it)
} }
@ -99,10 +121,9 @@ class NotificationService : JobService() {
private fun generalNotification(id: Int, textRes: Int, withDefaults: Boolean) { private fun generalNotification(id: Int, textRes: Int, withDefaults: Boolean) {
val notifBuilder = frostNotification(NOTIF_CHANNEL_GENERAL) val notifBuilder = frostNotification(NOTIF_CHANNEL_GENERAL)
.setFrostAlert(withDefaults, Prefs.notificationRingtone) .setFrostAlert(withDefaults, Prefs.notificationRingtone)
.setContentTitle(string(R.string.frost_name)) .setContentTitle(string(R.string.frost_name))
.setContentText(string(textRes)) .setContentText(string(textRes))
NotificationManagerCompat.from(this).notify(id, notifBuilder.build()) 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 package com.pitchedapps.frost.services
import android.app.Notification import android.app.Notification
@ -31,11 +47,11 @@ fun setupNotificationChannels(c: Context) {
val appName = c.string(R.string.frost_name) val appName = c.string(R.string.frost_name)
val msg = c.string(R.string.messages) val msg = c.string(R.string.messages)
manager.notificationChannels manager.notificationChannels
.filter { .filter {
it.id != NOTIF_CHANNEL_GENERAL it.id != NOTIF_CHANNEL_GENERAL &&
&& it.id != NOTIF_CHANNEL_MESSAGES it.id != NOTIF_CHANNEL_MESSAGES
} }
.forEach { manager.deleteNotificationChannel(it.id) } .forEach { manager.deleteNotificationChannel(it.id) }
manager.createNotificationChannel(NOTIF_CHANNEL_GENERAL, appName) manager.createNotificationChannel(NOTIF_CHANNEL_GENERAL, appName)
manager.createNotificationChannel(NOTIF_CHANNEL_MESSAGES, "$appName: $msg") manager.createNotificationChannel(NOTIF_CHANNEL_MESSAGES, "$appName: $msg")
L.d { "Created notification channels: ${manager.notificationChannels.size} channels, ${manager.notificationChannelGroups.size} groups" } 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) @RequiresApi(Build.VERSION_CODES.O)
private fun NotificationManager.createNotificationChannel(id: String, name: String): NotificationChannel { private fun NotificationManager.createNotificationChannel(id: String, name: String): NotificationChannel {
val channel = NotificationChannel(id, val channel = NotificationChannel(
name, NotificationManager.IMPORTANCE_DEFAULT) id,
name, NotificationManager.IMPORTANCE_DEFAULT
)
channel.enableLights(true) channel.enableLights(true)
channel.lightColor = Prefs.accentColor channel.lightColor = Prefs.accentColor
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
@ -53,14 +71,14 @@ private fun NotificationManager.createNotificationChannel(id: String, name: Stri
} }
fun Context.frostNotification(id: String) = fun Context.frostNotification(id: String) =
NotificationCompat.Builder(this, id) NotificationCompat.Builder(this, id)
.apply { .apply {
setSmallIcon(R.drawable.frost_f_24) setSmallIcon(R.drawable.frost_f_24)
setAutoCancel(true) setAutoCancel(true)
setOnlyAlertOnce(true) setOnlyAlertOnce(true)
setStyle(NotificationCompat.BigTextStyle()) setStyle(NotificationCompat.BigTextStyle())
color = color(R.color.frost_notification_accent) color = color(R.color.frost_notification_accent)
} }
/** /**
* Dictates whether a notification should have sound/vibration/lights or not * 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 { fun NotificationCompat.Builder.setFrostAlert(enable: Boolean, ringtone: String): NotificationCompat.Builder {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setGroupAlertBehavior( setGroupAlertBehavior(
if (enable) NotificationCompat.GROUP_ALERT_CHILDREN if (enable) NotificationCompat.GROUP_ALERT_CHILDREN
else NotificationCompat.GROUP_ALERT_SUMMARY) else NotificationCompat.GROUP_ALERT_SUMMARY
)
} else if (!enable) { } else if (!enable) {
setDefaults(0) setDefaults(0)
} else { } else {
@ -111,10 +130,10 @@ inline fun <reified T : JobService> Context.scheduleJob(id: Int, minutes: Long):
if (minutes < 0L) return true if (minutes < 0L) return true
val serviceComponent = ComponentName(this, T::class.java) val serviceComponent = ComponentName(this, T::class.java)
val builder = JobInfo.Builder(id, serviceComponent) val builder = JobInfo.Builder(id, serviceComponent)
.setPeriodic(minutes * 60000) .setPeriodic(minutes * 60000)
.setExtras(id) .setExtras(id)
.setPersisted(true) .setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //TODO add options .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //TODO add options
val result = scheduler.schedule(builder.build()) val result = scheduler.schedule(builder.build())
if (result <= 0) { if (result <= 0) {
L.eThrow("${T::class.java.simpleName} scheduler failed") 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 scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(this, T::class.java) val serviceComponent = ComponentName(this, T::class.java)
val builder = JobInfo.Builder(id, serviceComponent) val builder = JobInfo.Builder(id, serviceComponent)
.setMinimumLatency(0L) .setMinimumLatency(0L)
.setExtras(id) .setExtras(id)
.setOverrideDeadline(2000L) .setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
val result = scheduler.schedule(builder.build()) val result = scheduler.schedule(builder.build())
if (result <= 0) { if (result <= 0) {
L.eThrow("${T::class.java.simpleName} instant scheduler failed") 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 package com.pitchedapps.frost.services
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@ -18,5 +34,4 @@ class UpdateReceiver : BroadcastReceiver() {
L.d { "Frost has updated" } L.d { "Frost has updated" }
context.scheduleNotifications(Prefs.notificationFreq) //Update notifications 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 package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder 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.MainActivityLayout
import com.pitchedapps.frost.enums.Theme import com.pitchedapps.frost.enums.Theme
import com.pitchedapps.frost.injectors.CssAssets 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 import com.pitchedapps.frost.views.KPrefTextSeekbar
/** /**
@ -74,7 +99,6 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
allowCustomAlpha = false allowCustomAlpha = false
} }
colorPicker(R.string.background_color, Prefs::customBackgroundColor, { colorPicker(R.string.background_color, Prefs::customBackgroundColor, {
Prefs.customBackgroundColor = it Prefs.customBackgroundColor = it
bgCanvas.ripple(it, duration = 500L) bgCanvas.ripple(it, duration = 500L)
@ -146,17 +170,20 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.tint_nav_desc descRes = R.string.tint_nav_desc
} }
list.add(KPrefTextSeekbar( list.add(
KPrefTextSeekbar(
KPrefSeekbar.KPrefSeekbarBuilder( KPrefSeekbar.KPrefSeekbarBuilder(
globalOptions, globalOptions,
R.string.web_text_scaling, Prefs::webTextScaling) { R.string.web_text_scaling, Prefs::webTextScaling
) {
Prefs.webTextScaling = it Prefs.webTextScaling = it
setFrostResult(REQUEST_TEXT_ZOOM) setFrostResult(REQUEST_TEXT_ZOOM)
})) })
)
checkbox(R.string.enforce_black_media_bg, Prefs::blackMediaBg, { checkbox(R.string.enforce_black_media_bg, Prefs::blackMediaBg, {
Prefs.blackMediaBg = it Prefs.blackMediaBg = it
}) { }) {
descRes = R.string.enforce_black_media_bg_desc 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 package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -15,7 +31,10 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.fancy_animations_desc 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 descRes = R.string.overlay_swipe_desc
} }
@ -46,5 +65,4 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
checkbox(R.string.analytics, Prefs::analytics, { Prefs.analytics = it }) { checkbox(R.string.analytics, Prefs::analytics, { Prefs.analytics = it }) {
descRes = R.string.analytics_desc 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 package com.pitchedapps.frost.settings
import android.content.Context import android.content.Context
@ -77,25 +93,26 @@ fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = {
} }
} }
} }
} }
} }
} }
private fun Context.createEmail(parser: FrostParser<*>, content: Any?) = private fun Context.createEmail(parser: FrostParser<*>, content: Any?) =
sendFrostEmail("${string(R.string.debug_report)}: ${parser::class.java.simpleName}") { sendFrostEmail("${string(R.string.debug_report)}: ${parser::class.java.simpleName}") {
addItem("Url", parser.url) addItem("Url", parser.url)
addItem("Contents", "$content") addItem("Contents", "$content")
} }
private const val ZIP_NAME = "debug" private const val ZIP_NAME = "debug"
fun SettingsActivity.sendDebug(url: String, html: String?) { fun SettingsActivity.sendDebug(url: String, html: String?) {
val downloader = OfflineWebsite(url, FbCookie.webCookie ?: "", val downloader = OfflineWebsite(
baseUrl = FB_URL_BASE, url, FbCookie.webCookie ?: "",
html = html, baseUrl = FB_URL_BASE,
baseDir = DebugActivity.baseDir(this)) html = html,
baseDir = DebugActivity.baseDir(this)
)
val md = materialDialog { val md = materialDialog {
title(R.string.parsing_data) title(R.string.parsing_data)
@ -114,7 +131,8 @@ fun SettingsActivity.sendDebug(url: String, html: String?) {
it.dismiss() it.dismiss()
if (success) { if (success) {
val zipUri = it.context.frostUriFromFile( 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" } L.i { "Sending debug zip with uri $zipUri" }
sendFrostEmail(R.string.debug_report_email_title) { sendFrostEmail(R.string.debug_report_email_title) {
addItem("Url", url) 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 package com.pitchedapps.frost.settings
import android.util.Log import android.util.Log
@ -24,7 +40,6 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
// Experimental content starts here ------------------ // Experimental content starts here ------------------
// Experimental content ends here -------------------- // Experimental content ends here --------------------
checkbox(R.string.verbose_logging, Prefs::verboseLogging, { checkbox(R.string.verbose_logging, Prefs::verboseLogging, {
@ -41,4 +56,4 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
finish() 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 package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -66,4 +82,4 @@ fun SettingsActivity.getFeedPrefs(): KPrefAdapterBuilder.() -> Unit = {
}) { }) {
descRes = R.string.facebook_ads_desc 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 package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
@ -10,8 +26,10 @@ import com.pitchedapps.frost.utils.Prefs
*/ */
fun SettingsActivity.getNetworkPrefs(): KPrefAdapterBuilder.() -> Unit = { 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 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 package com.pitchedapps.frost.settings
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -22,7 +38,6 @@ import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.materialDialogThemed import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.views.Keywords import com.pitchedapps.frost.views.Keywords
/** /**
* Created by Allan Wang on 2017-06-29. * Created by Allan Wang on 2017-06-29.
*/ */
@ -66,33 +81,33 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
} }
checkbox(R.string.notification_general, Prefs::notificationsGeneral, checkbox(R.string.notification_general, Prefs::notificationsGeneral,
{ {
Prefs.notificationsGeneral = it Prefs.notificationsGeneral = it
reloadByTitle(R.string.notification_general_all_accounts) reloadByTitle(R.string.notification_general_all_accounts)
if (!Prefs.notificationsInstantMessages) if (!Prefs.notificationsInstantMessages)
reloadByTitle(R.string.notification_frequency) reloadByTitle(R.string.notification_frequency)
}) { }) {
descRes = R.string.notification_general_desc descRes = R.string.notification_general_desc
} }
checkbox(R.string.notification_general_all_accounts, Prefs::notificationAllAccounts, checkbox(R.string.notification_general_all_accounts, Prefs::notificationAllAccounts,
{ Prefs.notificationAllAccounts = it }) { { Prefs.notificationAllAccounts = it }) {
descRes = R.string.notification_general_all_accounts_desc descRes = R.string.notification_general_all_accounts_desc
enabler = Prefs::notificationsGeneral enabler = Prefs::notificationsGeneral
} }
checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages, checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages,
{ {
Prefs.notificationsInstantMessages = it Prefs.notificationsInstantMessages = it
reloadByTitle(R.string.notification_messages_all_accounts) reloadByTitle(R.string.notification_messages_all_accounts)
if (!Prefs.notificationsGeneral) if (!Prefs.notificationsGeneral)
reloadByTitle(R.string.notification_frequency) reloadByTitle(R.string.notification_frequency)
}) { }) {
descRes = R.string.notification_messages_desc descRes = R.string.notification_messages_desc
} }
checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts, checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts,
{ Prefs.notificationsImAllAccounts = it }) { { Prefs.notificationsImAllAccounts = it }) {
descRes = R.string.notification_messages_all_accounts_desc descRes = R.string.notification_messages_all_accounts_desc
enabler = Prefs::notificationsInstantMessages enabler = Prefs::notificationsInstantMessages
} }
@ -102,15 +117,17 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.notification_channel_desc descRes = R.string.notification_channel_desc
onClick = { onClick = {
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) .putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
startActivity(intent) startActivity(intent)
} }
} }
} else { } else {
checkbox(R.string.notification_sound, Prefs::notificationSound, { checkbox(R.string.notification_sound, Prefs::notificationSound, {
Prefs.notificationSound = it Prefs.notificationSound = it
reloadByTitle(R.string.notification_ringtone, reloadByTitle(
R.string.message_ringtone) R.string.notification_ringtone,
R.string.message_ringtone
)
}) })
fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) { fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) {
@ -118,8 +135,8 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
textGetter = { textGetter = {
if (it.isBlank()) string(R.string.kau_default) if (it.isBlank()) string(R.string.kau_default)
else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it)) else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it))
?.getTitle(this@getNotificationPrefs) ?.getTitle(this@getNotificationPrefs)
?: "---" //todo figure out why this happens ?: "---" //todo figure out why this happens
} }
onClick = { onClick = {
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
@ -135,20 +152,20 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
} }
text(R.string.notification_ringtone, Prefs::notificationRingtone, text(R.string.notification_ringtone, Prefs::notificationRingtone,
{ Prefs.notificationRingtone = it }) { { Prefs.notificationRingtone = it }) {
ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE) ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE)
} }
text(R.string.message_ringtone, Prefs::messageRingtone, text(R.string.message_ringtone, Prefs::messageRingtone,
{ Prefs.messageRingtone = it }) { { Prefs.messageRingtone = it }) {
ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE) ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE)
} }
checkbox(R.string.notification_vibrate, Prefs::notificationVibrate, checkbox(R.string.notification_vibrate, Prefs::notificationVibrate,
{ Prefs.notificationVibrate = it }) { Prefs.notificationVibrate = it })
checkbox(R.string.notification_lights, Prefs::notificationLights, checkbox(R.string.notification_lights, Prefs::notificationLights,
{ Prefs.notificationLights = it }) { Prefs.notificationLights = it })
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -165,10 +182,9 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.notification_fetch_now_desc descRes = R.string.notification_fetch_now_desc
onClick = { onClick = {
val text = val text =
if (fetchNotifications()) R.string.notification_fetch_success if (fetchNotifications()) R.string.notification_fetch_success
else R.string.notification_fetch_fail else R.string.notification_fetch_fail
frostSnackbar(text) 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 package com.pitchedapps.frost.utils
import android.content.Context import android.content.Context
@ -45,4 +61,4 @@ open class AdBlocker(val assetPath: String) {
if (host.contains(host)) return true if (host.contains(host)) return true
return isAdHost(host.substring(index + 1)) 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 package com.pitchedapps.frost.utils
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import androidx.annotation.DrawableRes
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.DrawableRes
import ca.allanwang.kau.utils.drawable import ca.allanwang.kau.utils.drawable
/** /**
@ -23,23 +39,23 @@ interface AnimatedVectorContract {
} }
class AnimatedVectorDelegate( class AnimatedVectorDelegate(
/** /**
* The res for the starting resource; must have parent tag animated-vector * The res for the starting resource; must have parent tag animated-vector
*/ */
@param:DrawableRes val avdStart: Int, @param:DrawableRes val avdStart: Int,
/** /**
* The res for the ending resource; must have parent tag animated-vector * The res for the ending resource; must have parent tag animated-vector
*/ */
@param:DrawableRes val avdEnd: Int, @param:DrawableRes val avdEnd: Int,
/** /**
* The delegate will automatically set the start resource when bound * The delegate will automatically set the start resource when bound
* If [emitOnBind] is true, it will also trigger the listener * If [emitOnBind] is true, it will also trigger the listener
*/ */
val emitOnBind: Boolean = true, val emitOnBind: Boolean = true,
/** /**
* The optional listener that will be triggered every time the avd is switched by the delegate * 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 override var animatedVectorListener: ((avd: AnimatedVectorDrawable, forwards: Boolean) -> Unit)? = null
) : AnimatedVectorContract { ) : AnimatedVectorContract {
lateinit var view: ImageView lateinit var view: ImageView
@ -55,9 +71,9 @@ class AnimatedVectorDelegate(
override fun bind(view: ImageView) { override fun bind(view: ImageView) {
this.view = view this.view = view
view.context.drawable(avdStart) as? AnimatedVectorDrawable 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 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) view.setImageResource(avdStart)
if (emitOnBind) animatedVectorListener?.invoke(avd!!, false) if (emitOnBind) animatedVectorListener?.invoke(avd!!, false)
} }
@ -70,15 +86,11 @@ class AnimatedVectorDelegate(
private fun animateImpl(toStart: Boolean) { private fun animateImpl(toStart: Boolean) {
if ((atStart == toStart)) return L.d { "AVD already at ${if (toStart) "start" else "end"}" } 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() avd?.stop()
view.setImageResource(if (toStart) avdEnd else avdStart) view.setImageResource(if (toStart) avdEnd else avdStart)
animatedVectorListener?.invoke(avd!!, !toStart) animatedVectorListener?.invoke(avd!!, !toStart)
atStart = toStart atStart = toStart
avd?.start() 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 package com.pitchedapps.frost.utils
object BuildUtils { object BuildUtils {
@ -18,8 +34,7 @@ object BuildUtils {
} }
fun getAllStages(): Array<String> = 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 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 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_NAV = 1 shl 15
const val REQUEST_SEARCH = 1 shl 16 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 package com.pitchedapps.frost.utils
import android.app.DownloadManager import android.app.DownloadManager
@ -16,26 +32,29 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbCookie
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
/** /**
* Created by Allan Wang on 2017-08-04. * 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> * 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?, fun Context.frostDownload(
userAgent: String = USER_AGENT_BASIC, url: String?,
contentDisposition: String? = null, userAgent: String = USER_AGENT_BASIC,
mimeType: String? = null, contentDisposition: String? = null,
contentLength: Long = 0L) { mimeType: String? = null,
contentLength: Long = 0L
) {
url ?: return url ?: return
frostDownload(Uri.parse(url), userAgent, contentDisposition, mimeType, contentLength) frostDownload(Uri.parse(url), userAgent, contentDisposition, mimeType, contentLength)
} }
fun Context.frostDownload(uri: Uri?, fun Context.frostDownload(
userAgent: String = USER_AGENT_BASIC, uri: Uri?,
contentDisposition: String? = null, userAgent: String = USER_AGENT_BASIC,
mimeType: String? = null, contentDisposition: String? = null,
contentLength: Long = 0L) { mimeType: String? = null,
contentLength: Long = 0L
) {
uri ?: return uri ?: return
L.d { "Received download request" } L.d { "Received download request" }
if (uri.scheme != "http" && uri.scheme != "https") { 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 package com.pitchedapps.frost.utils
import android.content.Intent 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(bundle: BaseBundle?) = get(bundle?.getString(argTag))
operator fun get(intent: Intent?) = get(intent?.getStringExtra(argTag)) operator fun get(intent: Intent?) = get(intent?.getStringExtra(argTag))
} }
open class EnumCompanion<E : Enum<E>>( open class EnumCompanion<E : Enum<E>>(
final override val argTag: String, final override val argTag: String,
final override val values: Array<E>) : EnumBundleCompanion<E> { final override val values: Array<E>
) : EnumBundleCompanion<E> {
final override val valueMap: Map<String, E> = values.map { it.name to it }.toMap() 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(bundle: BaseBundle?) = super.get(bundle)
final override fun get(intent: Intent?) = super.get(intent) 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 package com.pitchedapps.frost.utils
import org.jsoup.Jsoup import org.jsoup.Jsoup
@ -21,8 +37,10 @@ internal fun String.cleanJsoup(): String = Jsoup.clean(this, PrivacyWhitelist())
class PrivacyWhitelist : Whitelist() { class PrivacyWhitelist : Whitelist() {
val blacklistAttrs = arrayOf("style", "aria-label", "rel") val blacklistAttrs = arrayOf("style", "aria-label", "rel")
val blacklistTags = arrayOf("body", "html", "head", "i", "b", "u", "style", "script", val blacklistTags = arrayOf(
"br", "p", "span", "ul", "ol", "li") "body", "html", "head", "i", "b", "u", "style", "script",
"br", "p", "span", "ul", "ol", "li"
)
override fun isSafeAttribute(tagName: String, el: Element, attr: Attribute): Boolean { override fun isSafeAttribute(tagName: String, el: Element, attr: Attribute): Boolean {
val key = attr.key 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 package com.pitchedapps.frost.utils
import android.util.Log import android.util.Log
@ -5,7 +21,6 @@ import ca.allanwang.kau.logging.KauLogger
import com.bugsnag.android.Bugsnag import com.bugsnag.android.Bugsnag
import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.BuildConfig
/** /**
* Created by Allan Wang on 2017-05-28. * Created by Allan Wang on 2017-05-28.
* *
@ -45,5 +60,4 @@ object L : KauLogger("Frost", {
Bugsnag.notify(t) 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 package com.pitchedapps.frost.utils
import android.graphics.Color import android.graphics.Color
@ -71,8 +87,8 @@ object Prefs : KPref() {
get() = Prefs.bgColor.withAlpha(30) get() = Prefs.bgColor.withAlpha(30)
fun nativeBgColor(unread: Boolean) = Prefs.bgColor fun nativeBgColor(unread: Boolean) = Prefs.bgColor
.colorToForeground(if (unread) 0.7f else 0.0f) .colorToForeground(if (unread) 0.7f else 0.0f)
.withAlpha(30) .withAlpha(30)
val bgColor: Int val bgColor: Int
get() = t.bgColor 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 package com.pitchedapps.frost.utils
import ca.allanwang.kau.kpref.KPref import ca.allanwang.kau.kpref.KPref
@ -21,4 +37,3 @@ object Showcase : KPref() {
override fun deleteKeys() = arrayOf("shown_release") 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 package com.pitchedapps.frost.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -8,33 +24,59 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri 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.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView 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.EmailBuilder
import ca.allanwang.kau.email.sendEmail import ca.allanwang.kau.email.sendEmail
import ca.allanwang.kau.mediapicker.createMediaFile import ca.allanwang.kau.mediapicker.createMediaFile
import ca.allanwang.kau.mediapicker.createPrivateMediaFile 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 ca.allanwang.kau.xml.showChangelog
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.SnackbarContentLayout import com.google.android.material.snackbar.SnackbarContentLayout
import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R 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.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.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.apache.commons.text.StringEscapeUtils
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* import java.util.ArrayList
import java.util.Locale
/** /**
* Created by Allan Wang on 2017-06-03. * 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_TEXT = "arg_text"
const val ARG_COOKIE = "arg_cookie" 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 = { startActivity<T>(clearStack, intentBuilder = {
putParcelableArrayListExtra(EXTRA_COOKIES, cookieList) putParcelableArrayListExtra(EXTRA_COOKIES, cookieList)
}) })
@ -81,8 +126,10 @@ fun Context.launchWebOverlay(url: String) = launchWebOverlayImpl<WebOverlayActiv
fun Context.launchWebOverlayBasic(url: String) = launchWebOverlayImpl<WebOverlayBasicActivity>(url) fun Context.launchWebOverlayBasic(url: String) = launchWebOverlayImpl<WebOverlayBasicActivity>(url)
private fun Context.fadeBundle() = ActivityOptions.makeCustomAnimation(this, private fun Context.fadeBundle() = ActivityOptions.makeCustomAnimation(
android.R.anim.fade_in, android.R.anim.fade_out).toBundle() this,
android.R.anim.fade_in, android.R.anim.fade_out
).toBundle()
fun Context.launchImageActivity(imageUrl: String, text: String? = null, cookie: String? = null) { fun Context.launchImageActivity(imageUrl: String, text: String? = null, cookie: String? = null) {
startActivity<ImageActivity>(intentBuilder = { startActivity<ImageActivity>(intentBuilder = {
@ -174,7 +221,6 @@ inline fun Activity.setFrostColors(builder: ActivityThemeUtils.() -> Unit) {
themer.theme(this) themer.theme(this)
} }
fun frostEvent(name: String, vararg events: Pair<String, Any>) { fun frostEvent(name: String, vararg events: Pair<String, Any>) {
// todo bind // todo bind
L.v { "Event: $name ${events.joinToString(", ")}" } 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")) 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") @SuppressLint("RestrictedApi")
private inline fun frostSnackbar(crossinline builder: Snackbar.() -> Unit): Snackbar.() -> Unit = { private inline fun frostSnackbar(crossinline builder: Snackbar.() -> Unit): Snackbar.() -> Unit = {
@ -240,7 +288,7 @@ inline val String?.isFacebookUrl
*/ */
inline val String.isVideoUrl inline val String.isVideoUrl
get() = startsWith(VIDEO_REDIRECT) || 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 * [true] if url directly leads to a usable image
@ -271,22 +319,22 @@ inline val String?.isIndependent: Boolean
} }
val dependentSegments = arrayOf( val dependentSegments = arrayOf(
"photoset_token", "direct_action_execute", "messages/?pageNum", "sharer.php", "photoset_token", "direct_action_execute", "messages/?pageNum", "sharer.php",
"events/permalink", "events/feed/watch", "events/permalink", "events/feed/watch",
/* /*
* Add new members to groups * Add new members to groups
*/ */
"madminpanel", "madminpanel",
/** /**
* Editing images * Editing images
*/ */
"/confirmation/?", "/confirmation/?",
/* /*
* Facebook messages have the following cases for the tid query * Facebook messages have the following cases for the tid query
* mid* or id* for newer threads, which can be launched in new windows * 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 * or a hash for old threads, which must be loaded on old threads
*/ */
"messages/read/?tid=id", "messages/read/?tid=mid" "messages/read/?tid=id", "messages/read/?tid=mid"
) )
inline val String?.isExplicitIntent inline val String?.isExplicitIntent
@ -297,16 +345,20 @@ fun Context.frostChangelog() = showChangelog(R.xml.frost_changelog, Prefs.textCo
} }
fun Context.frostUriFromFile(file: File): Uri = fun Context.frostUriFromFile(file: File): Uri =
FileProvider.getUriForFile(this, FileProvider.getUriForFile(
BuildConfig.APPLICATION_ID + ".provider", this,
file) 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) { inline fun Context.sendFrostEmail(subjectId: String, crossinline builder: EmailBuilder.() -> Unit) =
builder() sendEmail(string(R.string.dev_email), subjectId) {
addFrostDetails() builder()
} addFrostDetails()
}
fun EmailBuilder.addFrostDetails() { fun EmailBuilder.addFrostDetails() {
addItem("Prev version", Prefs.prevVersionCode.toString()) addItem("Prev version", Prefs.prevVersionCode.toString())
@ -318,7 +370,8 @@ fun EmailBuilder.addFrostDetails() {
fun frostJsoup(url: String) = frostJsoup(FbCookie.webCookie, url) 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? { fun Element.first(vararg select: String): Element? {
select.forEach { select.forEach {
@ -346,6 +399,6 @@ fun File.createFreshDir(): Boolean {
} }
fun String.unescapeHtml(): String = fun String.unescapeHtml(): String =
StringEscapeUtils.unescapeXml(this) StringEscapeUtils.unescapeXml(this)
.replace("\\u003C", "<") .replace("\\u003C", "<")
.replace("\\\"", "\"") .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 package com.pitchedapps.frost.utils
import android.content.Context 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) { 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) }), 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_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) }), SHARE_LINK(R.string.share_link, { c, wc -> c.shareText(wc.url) }),
DEBUG_LINK(R.string.debug_link, { c, wc -> DEBUG_LINK(R.string.debug_link, { c, wc ->
c.materialDialogThemed { c.materialDialogThemed {
@ -63,4 +81,4 @@ enum class WebContextType(val textId: Int, val onClick: (c: Context, wc: WebCont
val values = values() val values = values()
operator fun get(index: Int) = values[index] 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 package com.pitchedapps.frost.views
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.iitems.KauIItem 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.DataSource
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
@ -23,7 +43,7 @@ import com.pitchedapps.frost.utils.Prefs
* Created by Allan Wang on 2017-06-05. * Created by Allan Wang on 2017-06-05.
*/ */
class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.ViewHolder> 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>) { override fun bindView(viewHolder: ViewHolder, payloads: MutableList<Any>) {
super.bindView(viewHolder, payloads) super.bindView(viewHolder, payloads)
@ -33,20 +53,37 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
if (cookie != null) { if (cookie != null) {
text.text = cookie.name text.text = cookie.name
GlideApp.with(itemView).load(profilePictureUrl(cookie.id)) GlideApp.with(itemView).load(profilePictureUrl(cookie.id))
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> { .transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { override fun onResourceReady(
text.fadeIn() resource: Drawable?,
return false 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 { override fun onLoadFailed(
text.fadeIn() e: GlideException?,
return false model: Any?,
} target: Target<Drawable>?,
}).into(image) isFirstResource: Boolean
): Boolean {
text.fadeIn()
return false
}
}).into(image)
} else { } else {
text.visible() 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) 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 image: ImageView by bindView(R.id.account_image)
val text: AppCompatTextView by bindView(R.id.account_text) 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 package com.pitchedapps.frost.views
import android.content.Context import android.content.Context
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import androidx.constraintlayout.widget.ConstraintLayout
import android.util.AttributeSet 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.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.R import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import kotlinx.android.synthetic.main.view_badged_icon.view.* import kotlinx.android.synthetic.main.view_badged_icon.view.*
/** /**
* Created by Allan Wang on 2017-06-19. * Created by Allan Wang on 2017-06-19.
*/ */
class BadgedIcon @JvmOverloads constructor( class BadgedIcon @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) { ) : ConstraintLayout(context, attrs, defStyleAttr) {
init { init {
inflate(context, R.layout.view_badged_icon, this) inflate(context, R.layout.view_badged_icon, this)
val badgeColor = Prefs.mainActivityLayout.backgroundColor().withAlpha(255).colorToForeground(0.2f) 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() badgeBackground.cornerRadius = 13.dpToPx.toFloat()
badge_text.background = badgeBackground badge_text.background = badgeBackground
badge_text.setTextColor(Prefs.mainActivityLayout.iconColor()) badge_text.setTextColor(Prefs.mainActivityLayout.iconColor())
@ -30,7 +53,13 @@ class BadgedIcon @JvmOverloads constructor(
var iicon: IIcon? = null var iicon: IIcon? = null
set(value) { set(value) {
field = 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) { fun setAllAlpha(alpha: Float) {
@ -46,5 +75,4 @@ class BadgedIcon @JvmOverloads constructor(
if (value != null && value != "0") badge_text.visible() if (value != null && value != "0") badge_text.visible()
else badge_text.gone() 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 package com.pitchedapps.frost.views
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ProgressBar 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.R
import com.pitchedapps.frost.contracts.FrostContentContainer import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.FrostContentCore import com.pitchedapps.frost.contracts.FrostContentCore
@ -24,25 +47,32 @@ import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
class FrostContentWeb @JvmOverloads constructor( 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) { ) : FrostContentView<FrostWebView>(context, attrs, defStyleAttr, defStyleRes) {
override val layoutRes: Int = R.layout.view_content_base_web override val layoutRes: Int = R.layout.view_content_base_web
} }
class FrostContentRecycler @JvmOverloads constructor( 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) { ) : FrostContentView<FrostRecyclerView>(context, attrs, defStyleAttr, defStyleRes) {
override val layoutRes: Int = R.layout.view_content_base_recycler override val layoutRes: Int = R.layout.view_content_base_recycler
} }
abstract class FrostContentView<out T> @JvmOverloads constructor( 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), ) : 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 refresh: SwipeRefreshLayout by bindView(R.id.content_refresh)
private val progress: ProgressBar by bindView(R.id.content_progress) private val progress: ProgressBar by bindView(R.id.content_progress)
@ -88,15 +118,14 @@ abstract class FrostContentView<out T> @JvmOverloads constructor(
}.addTo(compositeDisposable) }.addTo(compositeDisposable)
refreshObservable refreshObservable
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
refresh.isRefreshing = it refresh.isRefreshing = it
refresh.isEnabled = true refresh.isEnabled = true
}.addTo(compositeDisposable) }.addTo(compositeDisposable)
refresh.setOnRefreshListener { coreView.reload(true) } refresh.setOnRefreshListener { coreView.reload(true) }
reloadThemeSelf() reloadThemeSelf()
} }
override fun bind(container: FrostContentContainer) { override fun bind(container: FrostContentContainer) {
@ -151,24 +180,24 @@ abstract class FrostContentView<out T> @JvmOverloads constructor(
var loading = dispose != null var loading = dispose != null
dispose?.dispose() dispose?.dispose()
dispose = refreshObservable dispose = refreshObservable
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
if (it) { if (it) {
loading = true loading = true
transitionStart = System.currentTimeMillis() transitionStart = System.currentTimeMillis()
clearAnimation() clearAnimation()
if (isVisible) if (isVisible)
fadeOut(duration = 200L) fadeOut(duration = 200L)
} else if (loading) { } else if (loading) {
loading = false loading = false
if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY) if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY) else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" } L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
dispose?.dispose() dispose?.dispose()
dispose = null dispose = null
}
} }
}
} }
return true 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 package com.pitchedapps.frost.views
import android.content.Context import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View 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.circularReveal
import ca.allanwang.kau.utils.fadeOut import ca.allanwang.kau.utils.fadeOut
import com.pitchedapps.frost.contracts.FrostContentContainer import com.pitchedapps.frost.contracts.FrostContentContainer
@ -18,9 +34,11 @@ import com.pitchedapps.frost.utils.Prefs
* *
*/ */
class FrostRecyclerView @JvmOverloads constructor( class FrostRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr), ) : RecyclerView(context, attrs, defStyleAttr),
FrostContentCore { FrostContentCore {
override fun reload(animate: Boolean) = reloadBase(animate) override fun reload(animate: Boolean) = reloadBase(animate)
@ -102,5 +120,4 @@ class FrostRecyclerView @JvmOverloads constructor(
override fun reloadTextSizeSelf() { override fun reloadTextSizeSelf() {
// todo // 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 package com.pitchedapps.frost.views
import android.annotation.SuppressLint 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 * Parent must have layout with both height & width as match_parent
*/ */
class FrostVideoView @JvmOverloads constructor( class FrostVideoView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : VideoView(context, attrs, defStyleAttr) { ) : VideoView(context, attrs, defStyleAttr) {
/** /**
@ -105,7 +123,10 @@ class FrostVideoView @JvmOverloads constructor(
videoDimensions.set(dimen, dimen) videoDimensions.set(dimen, dimen)
} }
val portrait = height > width 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 desiredHeight = scale * videoDimensions.y
val desiredWidth = scale * videoDimensions.x val desiredWidth = scale * videoDimensions.x
val padding = containerContract.lowerVideoPadding val padding = containerContract.lowerVideoPadding
@ -151,8 +172,8 @@ class FrostVideoView @JvmOverloads constructor(
/** /**
* Only remap if not expanded and if dimensions have changed * Only remap if not expanded and if dimensions have changed
*/ */
val shouldRemap = !isExpanded val shouldRemap = !isExpanded &&
&& (videoDimensions.x != ratio * intrinsicWidth || videoDimensions.y != ratio * intrinsicHeight) (videoDimensions.x != ratio * intrinsicWidth || videoDimensions.y != ratio * intrinsicHeight)
videoDimensions.set(ratio * intrinsicWidth, ratio * intrinsicHeight) videoDimensions.set(ratio * intrinsicWidth, ratio * intrinsicHeight)
if (shouldRemap) updateLocation() 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) 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. * 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 gestureDetector: GestureDetector = GestureDetector(context, this)
private val downLoc = PointF() private val downLoc = PointF()
@ -315,4 +338,4 @@ class FrostVideoView @JvmOverloads constructor(
return true 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 package com.pitchedapps.frost.views
import android.content.Context import android.content.Context
@ -8,11 +24,22 @@ import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.FrameLayout 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.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener
import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.R import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostDownload import com.pitchedapps.frost.utils.frostDownload
import kotlinx.android.synthetic.main.view_video.view.* 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. * Created by Allan Wang on 2017-10-13.
*/ */
class FrostVideoViewer @JvmOverloads constructor( 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 { ) : FrameLayout(context, attrs, defStyleAttr), FrostVideoViewerContract {
companion object { companion object {
@ -51,16 +80,18 @@ class FrostVideoViewer @JvmOverloads constructor(
inflate(R.layout.view_video, true) inflate(R.layout.view_video, true)
alpha = 0f alpha = 0f
video_background.setBackgroundColor( video_background.setBackgroundColor(
if (!Prefs.blackMediaBg && Prefs.bgColor.isColorDark) if (!Prefs.blackMediaBg && Prefs.bgColor.isColorDark)
Prefs.bgColor.withMinAlpha(200) Prefs.bgColor.withMinAlpha(200)
else else
Color.BLACK) Color.BLACK
)
video.setViewerContract(this) video.setViewerContract(this)
video.pause() video.pause()
video_toolbar.inflateMenu(R.menu.menu_video) video_toolbar.inflateMenu(R.menu.menu_video)
context.setMenuIcons(video_toolbar.menu, Prefs.iconColor, context.setMenuIcons(
R.id.action_pip to GoogleMaterial.Icon.gmd_picture_in_picture_alt, video_toolbar.menu, Prefs.iconColor,
R.id.action_download to GoogleMaterial.Icon.gmd_file_download 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 { video_toolbar.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
@ -141,7 +172,6 @@ class FrostVideoViewer @JvmOverloads constructor(
if (!video_toolbar.isGone) if (!video_toolbar.isGone)
video_toolbar.fadeOut(duration = CONTROL_ANIMATION_DURATION) { video_toolbar.gone() } video_toolbar.fadeOut(duration = CONTROL_ANIMATION_DURATION) { video_toolbar.gone() }
} }
} }
interface FrostVideoViewerContract : VideoControlsVisibilityListener { interface FrostVideoViewerContract : VideoControlsVisibilityListener {
@ -171,4 +201,4 @@ interface FrostVideoContainerContract {
* Called once the video has stopped & should be removed * Called once the video has stopped & should be removed
*/ */
fun onVideoFinished() 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 package com.pitchedapps.frost.views
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import androidx.viewpager.widget.ViewPager
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
/** /**
@ -12,21 +28,22 @@ import com.pitchedapps.frost.utils.Prefs
* *
* Basic override to allow us to control swiping * 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 var enableSwipe = true
override fun onInterceptTouchEvent(ev: MotionEvent?) = override fun onInterceptTouchEvent(ev: MotionEvent?) =
try { try {
Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev) Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
false false
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent?): Boolean = override fun onTouchEvent(ev: MotionEvent?): Boolean =
try { try {
Prefs.viewpagerSwipe && enableSwipe && super.onTouchEvent(ev) Prefs.viewpagerSwipe && enableSwipe && super.onTouchEvent(ev)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
false 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 package com.pitchedapps.frost.views
import android.animation.ValueAnimator 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.fragments.WebFragment
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostDownload 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. * Created by Allan Wang on 2017-05-29.
* *
*/ */
class FrostWebView @JvmOverloads constructor( class FrostWebView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : NestedWebView(context, attrs, defStyleAttr), ) : NestedWebView(context, attrs, defStyleAttr),
FrostContentCore { FrostContentCore {
override fun reload(animate: Boolean) { override fun reload(animate: Boolean) {
if (parent.registerTransition(false, animate)) if (parent.registerTransition(false, animate))
@ -59,7 +81,6 @@ class FrostWebView @JvmOverloads constructor(
return this return this
} }
/** /**
* Wrapper to the main userAgentString to cache it. * Wrapper to the main userAgentString to cache it.
* This decouples it from the UiThread * This decouples it from the UiThread
@ -168,4 +189,4 @@ class FrostWebView @JvmOverloads constructor(
super.destroy() 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 package com.pitchedapps.frost.views
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -43,4 +59,4 @@ class KPrefTextSeekbar(builder: KPrefSeekbarContract) : KPrefSeekbar(builder) {
holder.desc?.setTextSize(TypedValue.COMPLEX_UNIT_PX, descOriginalSize) holder.desc?.setTextSize(TypedValue.COMPLEX_UNIT_PX, descOriginalSize)
super.unbindView(holder) 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 package com.pitchedapps.frost.views
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable 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.util.AttributeSet
import android.view.View import android.view.View
import android.widget.ImageView 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.bindView
import ca.allanwang.kau.utils.string import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.tint 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.R
import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Prefs
/** /**
* Created by Allan Wang on 2017-06-19. * Created by Allan Wang on 2017-06-19.
*/ */
class Keywords @JvmOverloads constructor( class Keywords @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) { ) : ConstraintLayout(context, attrs, defStyleAttr) {
val editText: AppCompatEditText by bindView(R.id.edit_text) val editText: AppCompatEditText by bindView(R.id.edit_text)
@ -51,7 +68,8 @@ class Keywords @JvmOverloads constructor(
recycler.layoutManager = LinearLayoutManager(context) recycler.layoutManager = LinearLayoutManager(context)
recycler.adapter = adapter recycler.adapter = adapter
adapter.withEventHook(object : ClickEventHook<KeywordItem>() { 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) { override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<KeywordItem>, item: KeywordItem) {
adapter.remove(position) adapter.remove(position)
@ -62,7 +80,6 @@ class Keywords @JvmOverloads constructor(
fun save() { fun save() {
Prefs.notificationKeywords = adapter.adapterItems.mapTo(mutableSetOf()) { it.keyword } Prefs.notificationKeywords = adapter.adapterItems.mapTo(mutableSetOf()) { it.keyword }
} }
} }
private fun IIcon.keywordDrawable(context: Context): Drawable = toDrawable(context, 20, Prefs.textColor) 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)) 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