aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-30 15:57:27 -0700
committerGitHub <noreply@github.com>2017-07-30 15:57:27 -0700
commit14185936f46160997ef9eaae92cb3c8eacae93c5 (patch)
tree616d132c4191cd025788e9d7faa9654cf20fa9fd /app/src/main/kotlin/com/pitchedapps
parentd94bc858c8a0c273d87d705eb06d35cfd9cf9e08 (diff)
downloadfrost-14185936f46160997ef9eaae92cb3c8eacae93c5.tar.gz
frost-14185936f46160997ef9eaae92cb3c8eacae93c5.tar.bz2
frost-14185936f46160997ef9eaae92cb3c8eacae93c5.zip
Intro (#100)v1.4.1
* Create base activity * Created some testers * Update theme and fix mess up * Update theme and replace paint * WIP intro drawables * Create intro screens * Clear unnecessary dependencies * Finalize intro panels * Clean up intro * Attack intro to settings * Fix lint * Finalize intro
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt14
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt169
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt10
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt51
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/intro/IntroImageFragments.kt112
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt135
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/AnimatedVectorDelegate.kt82
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Showcase.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt10
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt26
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt2
16 files changed, 606 insertions, 28 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
index fd020af1..6806bf24 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
@@ -1,11 +1,13 @@
package com.pitchedapps.frost.activities
+import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.pitchedapps.frost.R
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostTheme
+import org.jetbrains.anko.contentView
/**
* Created by Allan Wang on 2017-06-12.
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
index a7c59deb..2e4ae410 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -122,7 +122,7 @@ class ImageActivity : AppCompatActivity() {
} else {
photo.setImage(ImageSource.uri(it))
fabAction = FabStates.DOWNLOAD
- photo.animate().alpha(1f).scaleX(1f).scaleY(1f).withEndAction { fab.show() }.start()
+ photo.animate().alpha(1f).scaleXY(1f).withEndAction { fab.show() }.start()
}
})
} else {
@@ -283,9 +283,15 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
* If it's in view, give it some animations
*/
fun update(fab: FloatingActionButton) {
- fab.transition {
- setIcon(iicon, color = iconColor)
- backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ if (fab.isHidden) {
+ fab.setIcon(iicon, color = iconColor)
+ fab.backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ fab.show()
+ } else {
+ fab.fadeScaleTransition {
+ setIcon(iicon, color = iconColor)
+ backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ }
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt
new file mode 100644
index 00000000..28b8f466
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt
@@ -0,0 +1,169 @@
+package com.pitchedapps.frost.activities
+
+import android.animation.ValueAnimator
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.support.v4.app.FragmentManager
+import android.support.v4.app.FragmentPagerAdapter
+import android.support.v4.view.ViewPager
+import android.support.v7.app.AppCompatActivity
+import android.view.View
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.ImageButton
+import android.widget.ImageView
+import ca.allanwang.kau.ui.views.RippleCanvas
+import ca.allanwang.kau.ui.widgets.InkPageIndicator
+import ca.allanwang.kau.utils.*
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.intro.*
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.cookies
+import com.pitchedapps.frost.utils.launchNewTask
+import org.jetbrains.anko.find
+
+
+/**
+ * Created by Allan Wang on 2017-07-25.
+ */
+class IntroActivity : AppCompatActivity(), ViewPager.PageTransformer, ViewPager.OnPageChangeListener {
+
+ val ripple: RippleCanvas by bindView(R.id.intro_ripple)
+ val viewpager: ViewPager by bindView(R.id.intro_viewpager)
+ lateinit var adapter: IntroPageAdapter
+ val indicator: InkPageIndicator by bindView(R.id.intro_indicator)
+ val skip: Button by bindView(R.id.intro_skip)
+ val next: ImageButton by bindView(R.id.intro_next)
+ private var barHasNext = true
+
+ val fragments = listOf(
+ IntroFragmentWelcome(),
+ IntroFragmentTheme(),
+ IntroAccountFragment(),
+ IntroTabTouchFragment(),
+ IntroTabContextFragment(),
+ IntroFragmentEnd()
+ )
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_intro)
+ adapter = IntroPageAdapter(supportFragmentManager, fragments)
+ viewpager.apply {
+ setPageTransformer(true, this@IntroActivity)
+ addOnPageChangeListener(this@IntroActivity)
+ adapter = this@IntroActivity.adapter
+ }
+ indicator.setViewPager(viewpager)
+ next.setIcon(GoogleMaterial.Icon.gmd_navigate_next)
+ next.setOnClickListener {
+ if (barHasNext) viewpager.setCurrentItem(viewpager.currentItem + 1, true)
+ else finish(next.x + next.pivotX, next.y + next.pivotY)
+ }
+ ripple.set(Prefs.bgColor)
+ theme()
+ }
+
+ fun theme() {
+ statusBarColor = Prefs.headerColor
+ navigationBarColor = Prefs.headerColor
+ skip.setTextColor(Prefs.textColor)
+ next.imageTintList = ColorStateList.valueOf(Prefs.textColor)
+ indicator.setColour(Prefs.textColor)
+ indicator.invalidate()
+ fragments.forEach { it.themeFragment() }
+ }
+
+ /**
+ * Transformations are mainly handled on a per view basis
+ * This sifies it by making the first fragment fade out as the second fragment comes in
+ * All fragments are locked in position
+ */
+ override fun transformPage(page: View, position: Float) {
+ //only apply to adjacent pages
+ if ((position < 0 && position > -1) || (position > 0 && position < 1)) {
+ val pageWidth = page.width
+ val translateValue = position * -pageWidth
+ page.translationX = (if (translateValue > -pageWidth) translateValue else 0f)
+ page.alpha = if (position < 0) 1 + position else 1f
+ } else {
+ page.alpha = 1f
+ page.translationX = 0f
+ }
+
+ }
+
+ fun finish(x: Float, y: Float) {
+ val blue = color(R.color.facebook_blue)
+ window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
+ ripple.ripple(blue, x, y, 600) {
+ postDelayed(1000) { finish() }
+ }
+ arrayOf(skip, indicator, next, fragments.last().view!!.find<View>(R.id.intro_title), fragments.last().view!!.find<View>(R.id.intro_desc)).forEach {
+ it.animate().alpha(0f).setDuration(600).start()
+ }
+ if (Prefs.textColor != Color.WHITE) {
+ val f = fragments.last().view!!.find<ImageView>(R.id.intro_image).drawable
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ addUpdateListener {
+ f.setTint(Prefs.textColor.blendWith(Color.WHITE, it.animatedValue as Float))
+ }
+ duration = 600
+ start()
+ }
+ }
+ if (Prefs.headerColor != blue) {
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ addUpdateListener {
+ val c = Prefs.headerColor.blendWith(blue, it.animatedValue as Float)
+ statusBarColor = c
+ navigationBarColor = c
+ }
+ duration = 600
+ start()
+ }
+ }
+ }
+
+ override fun finish() {
+ launchNewTask(MainActivity::class.java, cookies())
+ super.finish()
+ }
+
+ override fun onBackPressed() {
+ if (viewpager.currentItem > 0) viewpager.setCurrentItem(viewpager.currentItem - 1, true)
+ else finish()
+ }
+
+ override fun onPageScrollStateChanged(state: Int) {
+
+ }
+
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+ fragments[position].onPageScrolled(positionOffset)
+ if (position + 1 < fragments.size)
+ fragments[position + 1].onPageScrolled(positionOffset - 1)
+ }
+
+ override fun onPageSelected(position: Int) {
+ fragments[position].onPageSelected()
+ val hasNext = position != fragments.size - 1
+ if (barHasNext == hasNext) return
+ barHasNext = hasNext
+ next.fadeScaleTransition {
+ setIcon(if (barHasNext) GoogleMaterial.Icon.gmd_navigate_next else GoogleMaterial.Icon.gmd_done, color = Prefs.textColor)
+ }
+ skip.animate().scaleXY(if (barHasNext) 1f else 0f)
+ }
+
+ class IntroPageAdapter(fm: FragmentManager, private val fragments: List<BaseIntroFragment>) : FragmentPagerAdapter(fm) {
+
+ override fun getItem(position: Int): Fragment = fragments[position]
+
+ override fun getCount(): Int = fragments.size
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
index 2dccbeb5..8503145e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -96,7 +96,8 @@ class LoginActivity : BaseActivity() {
loadFbCookiesAsync {
cookies ->
Handler().postDelayed({
- launchNewTask(MainActivity::class.java, ArrayList(cookies), clearStack = true)
+ launchNewTask(if (Showcase.intro) IntroActivity::class.java else MainActivity::class.java,
+ ArrayList(cookies), clearStack = true)
}, 1000)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
index 978659db..a6396b1b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -86,6 +86,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
var hiddenSearchView: SearchWebView? = null
var firstLoadFinished = false
set(value) {
+ if (field && value) return //both vals are already true
L.d("First fragment load has finished")
field = value
if (value && hiddenSearchView == null) {
@@ -160,6 +161,14 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
// }
setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager))
onCreateBilling()
+ if (Prefs.installDate < 1501454310304 && Showcase.intro)
+ materialDialogThemed {
+ title(R.string.intro_title)
+ content(R.string.intro_desc)
+ positiveText(R.string.kau_yes)
+ negativeText(R.string.kau_no)
+ onPositive { _, _ -> launchIntroActivity(cookies()) }
+ }
}
fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) {
@@ -383,6 +392,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
when (item.itemId) {
R.id.action_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
+ intent.putParcelableArrayListExtra(EXTRA_COOKIES, cookies())
val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle()
startActivityForResult(intent, ACTIVITY_SETTINGS, bundle)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
index d073050b..8455bf1e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
@@ -79,6 +79,11 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
onClick = { _, _, _ -> kauLaunchAbout(AboutActivity::class.java); true }
}
+ plainText(R.string.replay_intro) {
+ iicon = GoogleMaterial.Icon.gmd_replay
+ onClick = {_,_,_-> launchIntroActivity(cookies()); true}
+ }
+
if (BuildConfig.DEBUG) {
checkbox(R.string.custom_pro, { Prefs.debugPro }, { Prefs.debugPro = it })
}
@@ -116,8 +121,6 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
}
}
-
-
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_settings, menu)
toolbar.tint(Prefs.iconColor)
@@ -143,7 +146,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
}
fun setFrostResult(flag: Int) {
- resultFlag = resultFlag and flag
+ resultFlag = resultFlag or flag
}
override fun onDestroy() {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
index 2f2050cc..ac979c85 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
@@ -38,6 +38,7 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract {
.replace("\$BT\$", bt)
.replace("\$BBT\$", bbt.toRgbaString())
.replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
+ .replace("\$OO\$", Prefs.bgColor.colorToForeground(0.35f).withAlpha(255).toRgbaString())
.replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
}
injector = JsBuilder().css(content).build()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt
new file mode 100644
index 00000000..d1d64712
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt
@@ -0,0 +1,51 @@
+package com.pitchedapps.frost.intro
+
+import android.os.Bundle
+import android.view.View
+import ca.allanwang.kau.utils.bindViewResettable
+import ca.allanwang.kau.utils.scaleXY
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.IntroActivity
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.Theme
+
+/**
+ * Created by Allan Wang on 2017-07-28.
+ */
+class IntroFragmentTheme : BaseIntroFragment(R.layout.intro_theme) {
+
+ val light: View by bindViewResettable(R.id.intro_theme_light)
+ val dark: View by bindViewResettable(R.id.intro_theme_dark)
+ val amoled: View by bindViewResettable(R.id.intro_theme_amoled)
+ val glass: View by bindViewResettable(R.id.intro_theme_glass)
+
+ val themeList
+ get() = listOf(light, dark, amoled, glass)
+
+ override fun viewArray(): Array<Array<out View>>
+ = arrayOf(arrayOf(title), arrayOf(light, dark), arrayOf(amoled, glass))
+
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ light.setThemeClick(Theme.LIGHT)
+ dark.setThemeClick(Theme.DARK)
+ amoled.setThemeClick(Theme.AMOLED)
+ glass.setThemeClick(Theme.GLASS)
+ val currentTheme = Prefs.theme - 1
+ if (currentTheme in 0..3)
+ themeList.forEachIndexed { index, v -> v.scaleXY = if (index == currentTheme) 1.6f else 0.8f }
+ }
+
+ private fun View.setThemeClick(theme: Theme) {
+ setOnClickListener {
+ v ->
+ Prefs.theme = theme.ordinal
+ (activity as IntroActivity).apply {
+ ripple.ripple(Prefs.bgColor, v.x + v.pivotX, v.y + v.pivotY)
+ theme()
+ }
+ themeList.forEach { it.animate().scaleXY(if (it == this) 1.6f else 0.8f).start() }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroImageFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroImageFragments.kt
new file mode 100644
index 00000000..d19a488d
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroImageFragments.kt
@@ -0,0 +1,112 @@
+package com.pitchedapps.frost.intro
+
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Bundle
+import android.view.View
+import ca.allanwang.kau.utils.colorToForeground
+import ca.allanwang.kau.utils.tint
+import ca.allanwang.kau.utils.withAlpha
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.utils.Prefs
+
+/**
+ * Created by Allan Wang on 2017-07-28.
+ */
+abstract class BaseImageIntroFragment(val titleRes: Int, val imageRes: Int, val descRes: Int) : BaseIntroFragment(R.layout.intro_image) {
+
+ val imageDrawable: LayerDrawable by lazyResettableRegistered { image.drawable as LayerDrawable }
+ val phone: Drawable by lazyResettableRegistered { imageDrawable.findDrawableByLayerId(R.id.intro_phone) }
+ val screen: Drawable by lazyResettableRegistered { imageDrawable.findDrawableByLayerId(R.id.intro_phone_screen) }
+
+ override fun viewArray(): Array<Array<out View>>
+ = arrayOf(arrayOf(title), arrayOf(desc))
+
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ title.setText(titleRes)
+ image.setImageResource(imageRes)
+ desc.setText(descRes)
+ super.onViewCreated(view, savedInstanceState)
+ }
+
+ override fun themeFragmentImpl() {
+ super.themeFragmentImpl()
+ title.setTextColor(Prefs.textColor)
+ desc.setTextColor(Prefs.textColor)
+ phone.tint(Prefs.textColor)
+ screen.tint(Prefs.bgColor)
+ }
+
+ fun themeImageComponent(color: Int, vararg id: Int) {
+ id.forEach { imageDrawable.findDrawableByLayerId(it).tint(color) }
+ }
+
+ override fun onPageScrolledImpl(positionOffset: Float) {
+ super.onPageScrolledImpl(positionOffset)
+ val alpha = ((1 - Math.abs(positionOffset)) * 255).toInt()
+ //apply alpha to all layers except the phone base
+ (0 until imageDrawable.numberOfLayers).forEach {
+ val d = imageDrawable.getDrawable(it)
+ if (d != phone) d.alpha = alpha
+ }
+ }
+
+ fun firstImageFragmentTransition(offset: Float) {
+ if (offset < 0)
+ image.alpha = 1 + offset
+ }
+
+ fun lastImageFragmentTransition(offset: Float) {
+ if (offset > 0)
+ image.alpha = 1 - offset
+ }
+}
+
+class IntroAccountFragment : BaseImageIntroFragment(
+ R.string.intro_multiple_accounts, R.drawable.intro_phone_nav, R.string.intro_multiple_accounts_desc
+) {
+
+ override fun themeFragmentImpl() {
+ super.themeFragmentImpl()
+ themeImageComponent(Prefs.iconColor, R.id.intro_phone_avatar_1, R.id.intro_phone_avatar_2)
+ themeImageComponent(Prefs.bgColor.colorToForeground(), R.id.intro_phone_nav)
+ themeImageComponent(Prefs.headerColor, R.id.intro_phone_header)
+ }
+
+ override fun onPageScrolledImpl(positionOffset: Float) {
+ super.onPageScrolledImpl(positionOffset)
+ firstImageFragmentTransition(positionOffset)
+ }
+}
+
+class IntroTabTouchFragment : BaseImageIntroFragment(
+ R.string.intro_easy_navigation, R.drawable.intro_phone_tab, R.string.intro_easy_navigation_desc
+) {
+
+ override fun themeFragmentImpl() {
+ super.themeFragmentImpl()
+ themeImageComponent(Prefs.iconColor, R.id.intro_phone_icon_1, R.id.intro_phone_icon_2, R.id.intro_phone_icon_3, R.id.intro_phone_icon_4)
+ themeImageComponent(Prefs.headerColor, R.id.intro_phone_tab)
+ themeImageComponent(Prefs.textColor.withAlpha(80), R.id.intro_phone_icon_ripple)
+ }
+}
+
+class IntroTabContextFragment : BaseImageIntroFragment(
+ R.string.intro_context_aware, R.drawable.intro_phone_long_press, R.string.intro_context_aware_desc
+) {
+
+ override fun themeFragmentImpl() {
+ super.themeFragmentImpl()
+ themeImageComponent(Prefs.headerColor, R.id.intro_phone_toolbar)
+ themeImageComponent(Prefs.bgColor.colorToForeground(0.1f), R.id.intro_phone_image)
+ themeImageComponent(Prefs.bgColor.colorToForeground(0.2f), R.id.intro_phone_like, R.id.intro_phone_share)
+ themeImageComponent(Prefs.bgColor.colorToForeground(0.3f), R.id.intro_phone_comment)
+ themeImageComponent(Prefs.bgColor.colorToForeground(0.1f), R.id.intro_phone_card_1, R.id.intro_phone_card_2)
+ themeImageComponent(Prefs.textColor, R.id.intro_phone_image_indicator, R.id.intro_phone_comment_indicator, R.id.intro_phone_card_indicator)
+ }
+
+ override fun onPageScrolledImpl(positionOffset: Float) {
+ super.onPageScrolledImpl(positionOffset)
+ lastImageFragmentTransition(positionOffset)
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt
new file mode 100644
index 00000000..552fad3b
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt
@@ -0,0 +1,135 @@
+package com.pitchedapps.frost.intro
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.support.constraint.ConstraintLayout
+import android.support.v4.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import ca.allanwang.kau.kotlin.LazyResettableRegistry
+import ca.allanwang.kau.utils.Kotterknife
+import ca.allanwang.kau.utils.bindViewResettable
+import ca.allanwang.kau.utils.setOnSingleTapListener
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.IntroActivity
+import com.pitchedapps.frost.utils.Prefs
+import org.jetbrains.anko.childrenSequence
+
+/**
+ * Created by Allan Wang on 2017-07-28.
+ *
+ * Contains the base, start, and end fragments
+ */
+
+/**
+ * The core intro fragment for all other fragments
+ */
+abstract class BaseIntroFragment(val layoutRes: Int) : Fragment() {
+
+ val screenWidth
+ get() = resources.displayMetrics.widthPixels
+
+ val lazyRegistry = LazyResettableRegistry()
+
+ protected fun translate(offset: Float, views: Array<Array<out View>>) {
+ val maxTranslation = offset * screenWidth
+ val increment = maxTranslation / views.size
+ views.forEachIndexed { i, group ->
+ group.forEach {
+ it.translationX = if (offset > 0) -maxTranslation + i * increment else -(i + 1) * increment
+ it.alpha = 1 - Math.abs(offset)
+ }
+ }
+ }
+
+ fun <T : Any> lazyResettableRegistered(initializer: () -> T) = lazyRegistry.lazy(initializer)
+
+ /*
+ * Note that these ids aren't actually inside all layouts
+ * However, they are in most of them, so they are added here
+ * for convenience
+ */
+ protected val title: TextView by bindViewResettable(R.id.intro_title)
+ protected val image: ImageView by bindViewResettable(R.id.intro_image)
+ protected val desc: TextView by bindViewResettable(R.id.intro_desc)
+
+ protected fun defaultViewArray(): Array<Array<out View>> = arrayOf(arrayOf(title), arrayOf(image), arrayOf(desc))
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val view = inflater.inflate(layoutRes, container, false)
+ return view
+ }
+
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ themeFragment()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ Kotterknife.reset(this)
+ lazyRegistry.invalidateAll()
+ }
+
+ fun themeFragment() {
+ if (view != null) themeFragmentImpl()
+ }
+
+ protected open fun themeFragmentImpl() {
+ view?.childrenSequence()?.forEach { (it as? TextView)?.setTextColor(Prefs.textColor) }
+ }
+
+ protected val viewArray: Array<Array<out View>> by lazyResettableRegistered { viewArray() }
+
+ protected abstract fun viewArray(): Array<Array<out View>>
+
+ fun onPageScrolled(positionOffset: Float) {
+ if (view != null) onPageScrolledImpl(positionOffset)
+ }
+
+ protected open fun onPageScrolledImpl(positionOffset: Float) {
+ translate(positionOffset, viewArray)
+ }
+
+ fun onPageSelected() {
+ if (view != null) onPageSelectedImpl()
+ }
+
+ protected open fun onPageSelectedImpl() {
+
+ }
+}
+
+class IntroFragmentWelcome : BaseIntroFragment(R.layout.intro_welcome) {
+
+ override fun viewArray(): Array<Array<out View>> = defaultViewArray()
+
+ override fun themeFragmentImpl() {
+ super.themeFragmentImpl()
+ image.imageTintList = ColorStateList.valueOf(Prefs.textColor)
+ }
+}
+
+class IntroFragmentEnd : BaseIntroFragment(R.layout.intro_end) {
+
+ val container: ConstraintLayout by bindViewResettable(R.id.intro_end_container)
+
+ override fun viewArray(): Array<Array<out View>> = defaultViewArray()
+
+ override fun themeFragmentImpl() {
+ super.themeFragmentImpl()
+ image.imageTintList = ColorStateList.valueOf(Prefs.textColor)
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ container.setOnSingleTapListener { _, event ->
+ (activity as IntroActivity).finish(event.x, event.y)
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/AnimatedVectorDelegate.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/AnimatedVectorDelegate.kt
new file mode 100644
index 00000000..a530df32
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/AnimatedVectorDelegate.kt
@@ -0,0 +1,82 @@
+package com.pitchedapps.frost.utils
+
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.support.annotation.DrawableRes
+import android.widget.ImageView
+import ca.allanwang.kau.utils.drawable
+
+/**
+ * Created by Allan Wang on 2017-07-29.
+ *
+ * Delegate for animated vector drawables with two states (start and end)
+ * Drawables are added lazily depending on the animation direction, and are verified upon load
+ * Should the bounded view not have an animated drawable upon animating, it is assumed
+ * that the user has switched the resource themselves and the delegate will not switch the resource
+ */
+interface AnimatedVectorContract {
+ fun animate()
+ fun animateReverse()
+ fun animateToggle()
+ val isAtStart: Boolean
+ fun bind(view: ImageView)
+ var animatedVectorListener: ((avd: AnimatedVectorDrawable, forwards: Boolean) -> Unit)?
+}
+
+class AnimatedVectorDelegate(
+ /**
+ * The res for the starting resource; must have parent tag animated-vector
+ */
+ @param:DrawableRes val avdStart: Int,
+ /**
+ * The res for the ending resource; must have parent tag animated-vector
+ */
+ @param:DrawableRes val avdEnd: Int,
+ /**
+ * The delegate will automatically set the start resource when bound
+ * If [emitOnBind] is true, it will also trigger the listener
+ */
+ val emitOnBind: Boolean = true,
+ /**
+ * The optional listener that will be triggered every time the avd is switched by the delegate
+ */
+ override var animatedVectorListener: ((avd: AnimatedVectorDrawable, forwards: Boolean) -> Unit)? = null
+) : AnimatedVectorContract {
+
+ lateinit var view: ImageView
+
+ private var atStart = true
+
+ override val isAtStart: Boolean
+ get() = atStart
+
+ private val avd: AnimatedVectorDrawable?
+ get() = view.drawable as? AnimatedVectorDrawable
+
+ override fun bind(view: ImageView) {
+ this.view = view
+ view.context.drawable(avdStart) as? AnimatedVectorDrawable ?: throw IllegalArgumentException("AnimatedVectorDelegate has a starting drawable that isn't an avd")
+ view.context.drawable(avdEnd) as? AnimatedVectorDrawable ?: throw IllegalArgumentException("AnimatedVectorDelegate has an ending drawable that isn't an avd")
+ view.setImageResource(avdStart)
+ if (emitOnBind) animatedVectorListener?.invoke(avd!!, false)
+ }
+
+ override fun animate() = animateImpl(false)
+
+ override fun animateReverse() = animateImpl(true)
+
+ override fun animateToggle() = animateImpl(!atStart)
+
+ private fun animateImpl(toStart: Boolean) {
+ if ((atStart == toStart)) return L.d("AVD already at ${if (toStart) "start" else "end"}")
+ if (avd == null) return L.d("AVD null resource")//no longer using animated vector; do not modify
+ avd?.stop()
+ view.setImageResource(if (toStart) avdEnd else avdStart)
+ animatedVectorListener?.invoke(avd!!, !toStart)
+ atStart = toStart
+ avd?.start()
+ }
+
+}
+
+
+
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Showcase.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Showcase.kt
index 57cbef7e..b3601dfb 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Showcase.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Showcase.kt
@@ -14,6 +14,8 @@ object Showcase : KPref() {
//check if this is the first time launching the web overlay; show snackbar if true
val firstWebOverlay: Boolean by kprefSingle("first_web_overlay")
+ val intro: Boolean by kprefSingle("intro_pages")
+
//not a showcase but cannot be in the same file as Prefs
var experimentalDefault: Boolean by kpref("experimental_by_default", false)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
index 442216fb..40e16f20 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -20,10 +20,7 @@ import com.crashlytics.android.answers.Answers
import com.crashlytics.android.answers.CustomEvent
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.activities.ImageActivity
-import com.pitchedapps.frost.activities.LoginActivity
-import com.pitchedapps.frost.activities.SelectorActivity
-import com.pitchedapps.frost.activities.WebOverlayActivity
+import com.pitchedapps.frost.activities.*
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.facebook.formattedFbUrl
@@ -68,6 +65,9 @@ fun Context.launchImageActivity(imageUrl: String, text: String?) {
})
}
+fun Activity.launchIntroActivity(cookieList: ArrayList<CookieModel>)
+ = launchNewTask(IntroActivity::class.java, cookieList, true)
+
fun WebOverlayActivity.url(): String {
return intent.extras?.getString(ARG_URL) ?: FbTab.FEED.url
}
@@ -140,4 +140,4 @@ fun Activity.frostNavigationBar() {
navigationBarColor = if (Prefs.tintNavBar) Prefs.headerColor else Color.BLACK
}
-fun <T> RequestBuilder<T>.withRoundIcon() = apply(RequestOptions().transform(CircleCrop())) \ No newline at end of file
+fun <T> RequestBuilder<T>.withRoundIcon() = apply(RequestOptions().transform(CircleCrop()))!! \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
index ab9e37d1..b3992ff4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
@@ -34,7 +34,7 @@ abstract class IABBinder : FrostBilling {
override fun Activity.onCreateBilling() {
activity = this
bp = BillingProcessor.newBillingProcessor(this, PUBLIC_BILLING_KEY, this@IABBinder)
- bp!!.initialize()
+ bp?.initialize()
}
override fun onDestroyBilling() {
@@ -79,10 +79,10 @@ abstract class IABBinder : FrostBilling {
L.eThrow("IAB null bp on purchase attempt")
return
}
- if (!bp!!.isOneTimePurchaseSupported)
- activity!!.playStorePurchaseUnsupported()
+ if (!(bp?.isOneTimePurchaseSupported ?: false))
+ activity?.playStorePurchaseUnsupported()
else
- bp!!.purchase(activity, FROST_PRO)
+ bp?.purchase(activity, FROST_PRO)
}
}
@@ -104,14 +104,14 @@ class IABSettings : IABBinder() {
*/
override fun restorePurchases() {
if (bp == null) return
- val load = bp!!.loadOwnedPurchasesFromGoogle()
+ val load = bp?.loadOwnedPurchasesFromGoogle() ?: return
L.d("IAB settings load from google $load")
- if (!bp!!.isPurchased(FROST_PRO)) {
- if (Prefs.pro) activity!!.playStoreNoLongerPro()
+ if (!(bp?.isPurchased(FROST_PRO) ?: return)) {
+ if (Prefs.pro) activity.playStoreNoLongerPro()
else purchasePro()
} else {
- if (!Prefs.pro) activity!!.playStoreFoundPro()
- else activity!!.purchaseRestored()
+ if (!Prefs.pro) activity.playStoreFoundPro()
+ else activity?.purchaseRestored()
}
}
}
@@ -138,12 +138,12 @@ class IABMain : IABBinder() {
override fun restorePurchases() {
if (restored || bp == null) return
restored = true
- val load = bp!!.loadOwnedPurchasesFromGoogle()
+ val load = bp?.loadOwnedPurchasesFromGoogle() ?: false
L.d("IAB main load from google $load")
- if (!bp!!.isPurchased(FROST_PRO)) {
- if (Prefs.pro) activity!!.playStoreNoLongerPro()
+ if (!(bp?.isPurchased(FROST_PRO) ?: false)) {
+ if (Prefs.pro) activity.playStoreNoLongerPro()
} else {
- if (!Prefs.pro) activity!!.playStoreFoundPro()
+ if (!Prefs.pro) activity.playStoreFoundPro()
}
onDestroyBilling()
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt
index d2f22829..df0f04fd 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt
@@ -31,7 +31,7 @@ private fun Activity.playRestart() {
} else restart()
}
-fun Activity.playStoreNoLongerPro() {
+fun Activity?.playStoreNoLongerPro() {
Prefs.pro = false
L.d("IAB No longer pro")
frostAnswers {
@@ -39,6 +39,7 @@ fun Activity.playStoreNoLongerPro() {
.putCustomAttribute("result", "no longer pro")
.putSuccess(false))
}
+ if (this == null) return
materialDialogThemed {
title(R.string.uh_oh)
content(R.string.play_store_not_pro)
@@ -49,9 +50,10 @@ fun Activity.playStoreNoLongerPro() {
}
}
-fun Activity.playStoreFoundPro() {
+fun Activity?.playStoreFoundPro() {
Prefs.pro = true
L.d("Found pro")
+ if (this == null) return
materialDialogThemed {
title(R.string.found_pro)
content(R.string.found_pro_desc)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt
index 8ae54ef3..df468715 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt
@@ -1,6 +1,8 @@
package com.pitchedapps.frost.views
import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet