[UI/Login] Add new easter eggs.

This commit is contained in:
Kuba Szczodrzyński 2020-10-18 22:14:41 +02:00
parent 477730708f
commit ed3a245b51
9 changed files with 363 additions and 6 deletions

View File

@ -25,7 +25,6 @@ import kotlin.coroutines.CoroutineContext
class LoginActivity : AppCompatActivity(), CoroutineScope { class LoginActivity : AppCompatActivity(), CoroutineScope {
companion object { companion object {
private const val TAG = "LoginActivity" private const val TAG = "LoginActivity"
var thisOneIsTricky = 0
} }
private val app: App by lazy { applicationContext as App } private val app: App by lazy { applicationContext as App }
@ -42,6 +41,8 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
val profiles = mutableListOf<LoginSummaryAdapter.Item>() val profiles = mutableListOf<LoginSummaryAdapter.Item>()
val loginStores = mutableListOf<LoginStore>() val loginStores = mutableListOf<LoginStore>()
fun getRootView() = b.root
override fun onBackPressed() { override fun onBackPressed() {
val destination = nav.currentDestination ?: run { val destination = nav.currentDestination ?: run {
nav.navigateUp() nav.navigateUp()
@ -55,6 +56,11 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
return return
if (destination.id == R.id.loginFinishFragment) if (destination.id == R.id.loginFinishFragment)
return return
// eggs
if (destination.id == R.id.loginPrizeFragment) {
finish()
return
}
if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) { if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) {
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_CANCELED)
finish() finish()
@ -79,8 +85,6 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setTheme(R.style.AppTheme_Light) setTheme(R.style.AppTheme_Light)
thisOneIsTricky = -1
navOptions = NavOptions.Builder() navOptions = NavOptions.Builder()
.setEnterAnim(R.anim.slide_in_right) .setEnterAnim(R.anim.slide_in_right)
.setExitAnim(R.anim.slide_out_left) .setExitAnim(R.anim.slide_out_left)

View File

@ -4,12 +4,18 @@
package pl.szczodrzynski.edziennik.ui.modules.login package pl.szczodrzynski.edziennik.ui.modules.login
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
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.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Animation
import android.view.animation.RotateAnimation
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -27,6 +33,8 @@ import kotlin.coroutines.CoroutineContext
class LoginChooserFragment : Fragment(), CoroutineScope { class LoginChooserFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "LoginChooserFragment" private const val TAG = "LoginChooserFragment"
// eggs
var isRotated = false
} }
private lateinit var app: App private lateinit var app: App
@ -48,13 +56,33 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
return b.root return b.root
} }
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return if (!isAdded) return
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked) val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
LoginInfo.chooserList = LoginInfo.chooserList LoginInfo.chooserList = LoginInfo.chooserList
?: LoginInfo.list.toMutableList<Any>() ?: LoginInfo.list.toMutableList()
// eggs
if (isRotated) {
isRotated = false
LoginFormFragment.wantEggs = false
LoginInfo.chooserList = LoginInfo.list.toMutableList()
val anim = RotateAnimation(
180f,
0f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
)
anim.interpolator = AccelerateDecelerateInterpolator()
anim.duration = 500
anim.fillAfter = true
activity.getRootView().startAnimation(anim)
}
adapter.items = LoginInfo.chooserList!! adapter.items = LoginInfo.chooserList!!
b.list.adapter = adapter b.list.adapter = adapter
@ -68,6 +96,81 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
startActivity(Intent(activity, FeedbackActivity::class.java)) startActivity(Intent(activity, FeedbackActivity::class.java))
} }
// eggs
b.footnoteText.onClick {
if (!LoginFormFragment.wantEggs || isRotated)
return@onClick
val text = b.subtitleText.text.toString()
if (text.endsWith(".."))
b.subtitleText.text = text.substring(0, text.length - 2)
else
b.subtitleText.text = "$text..."
}
var clickCount = 0
val color = R.color.md_blue_500.resolveColor(app)
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
val hueOriginal = hsv[0]
b.subtitleText.onClick {
if (isRotated)
return@onClick
val text = b.subtitleText.text.toString()
if (text.endsWith("..") && !text.endsWith("...")) {
clickCount++
}
if (clickCount == 5) {
val anim = ValueAnimator.ofFloat(0f, 1f)
anim.duration = 5000
anim.addUpdateListener {
hsv[0] = hueOriginal + it.animatedFraction * 3f * 360f
hsv[0] = hsv[0] % 360f
b.topLogo.drawable.setTintColor(Color.HSVToColor(Color.alpha(color), hsv))
}
anim.start()
}
}
b.topLogo.onClick {
if (clickCount != 5 || isRotated) {
clickCount = 0
return@onClick
}
isRotated = true
val anim = RotateAnimation(
0f,
180f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
)
anim.interpolator = AccelerateDecelerateInterpolator()
anim.duration = 2000
anim.fillAfter = true
activity.getRootView().startAnimation(anim)
b.list.smoothScrollToPosition(0)
adapter.items.add(
LoginInfo.Register(
loginType = 74,
internalName = "eggs",
registerName = R.string.eggs,
registerLogo = R.drawable.face_1,
loginModes = listOf(
LoginInfo.Mode(
loginMode = 0,
name = 0,
icon = 0,
guideText = 0,
credentials = listOf(),
errorCodes = mapOf()
)
)
)
)
adapter.notifyItemInserted(adapter.items.size - 1)
}
when { when {
activity.loginStores.isNotEmpty() -> { activity.loginStores.isNotEmpty() -> {
// we are navigated here from LoginSummary // we are navigated here from LoginSummary
@ -93,6 +196,11 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
loginType: LoginInfo.Register, loginType: LoginInfo.Register,
loginMode: LoginInfo.Mode loginMode: LoginInfo.Mode
) { ) {
if (loginType.internalName == "eggs") {
nav.navigate(R.id.loginEggsFragment, null, activity.navOptions)
return
}
launch { launch {
if (!checkAvailability(loginType.loginType)) if (!checkAvailability(loginType.loginType))
return@launch return@launch

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-10-18.
*/
package pl.szczodrzynski.edziennik.ui.modules.login
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Animation
import android.view.animation.RotateAnimation
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.md5
import kotlin.coroutines.CoroutineContext
class LoginEggsFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "LoginEggsFragment"
}
private lateinit var app: App
private lateinit var activity: LoginActivity
private lateinit var view: ViewGroup
private lateinit var webView: WebView
private val nav by lazy { activity.nav }
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
webView = WebView(activity)
view = FrameLayout(activity)
view.addView(webView)
return view
}
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return
if (!LoginChooserFragment.isRotated) {
nav.navigateUp()
return
}
val anim = RotateAnimation(
180f,
0f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
)
anim.interpolator = AccelerateDecelerateInterpolator()
anim.duration = 10
anim.fillAfter = true
activity.getRootView().startAnimation(anim)
webView.apply {
settings.apply {
javaScriptEnabled = true
}
addJavascriptInterface(object : Any() {
@Suppress("NAME_SHADOWING")
@JavascriptInterface
fun getPrize() {
val anim = RotateAnimation(
0f,
180f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
)
anim.interpolator = AccelerateDecelerateInterpolator()
anim.duration = 10
anim.fillAfter = true
activity.getRootView().startAnimation(anim)
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
}
}, "EggInterface")
loadUrl("https://szkolny.eu/game/runner.html")
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val deviceId = app.deviceId.md5()
val version = BuildConfig.VERSION_NAME
val js = """initPage("$deviceId", true, "$version");"""
webView.evaluateJavascript(js) {}
}
}
}
}
}
}

View File

@ -32,6 +32,8 @@ import kotlin.coroutines.CoroutineContext
class LoginFormFragment : Fragment(), CoroutineScope { class LoginFormFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "LoginFormFragment" private const val TAG = "LoginFormFragment"
// eggs
var wantEggs = false
} }
private lateinit var app: App private lateinit var app: App
@ -108,8 +110,13 @@ class LoginFormFragment : Fragment(), CoroutineScope {
if (credential is LoginInfo.FormCheckbox) { if (credential is LoginInfo.FormCheckbox) {
val b = LoginFormCheckboxItemBinding.inflate(layoutInflater) val b = LoginFormCheckboxItemBinding.inflate(layoutInflater)
b.checkbox.text = app.getString(credential.name) b.checkbox.text = app.getString(credential.name)
b.checkbox.onChange { _, _ -> b.checkbox.onChange { _, isChecked ->
b.errorText.text = null b.errorText.text = null
// eggs
if (register.internalName == "podlasie") {
wantEggs = !isChecked
}
} }
if (arguments?.containsKey(credential.keyName) == true) { if (arguments?.containsKey(credential.keyName) == true) {
b.checkbox.isChecked = arguments?.getBoolean(credential.keyName) == true b.checkbox.isChecked = arguments?.getBoolean(credential.keyName) == true

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-10-18.
*/
package pl.szczodrzynski.edziennik.ui.modules.login
import android.os.Bundle
import android.os.Process
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import coil.api.load
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.LoginPrizeFragmentBinding
import pl.szczodrzynski.edziennik.onClick
import kotlin.coroutines.CoroutineContext
import kotlin.system.exitProcess
class LoginPrizeFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "LoginPrizeFragment"
}
private lateinit var app: App
private lateinit var activity: LoginActivity
private lateinit var b: LoginPrizeFragmentBinding
private val nav by lazy { activity.nav }
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
b = LoginPrizeFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
b.button.load("https://szkolny.eu/game/button.png")
b.button.onClick {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.are_you_sure)
.setMessage(R.string.dev_mode_enable_warning)
.setPositiveButton(R.string.yes) { _, _ ->
app.config.debugMode = true
App.devMode = true
MaterialAlertDialogBuilder(activity)
.setTitle("Restart")
.setMessage("Wymagany restart aplikacji")
.setPositiveButton(R.string.ok) { _, _ ->
Process.killProcess(Process.myPid())
Runtime.getRuntime().exit(0)
exitProcess(0)
}
.setCancelable(false)
.show()
}
.setNegativeButton(R.string.no) { _, _ ->
app.config.debugMode = false
App.devMode = false
activity.finish()
}
.show()
}
}
}

View File

@ -31,6 +31,7 @@
android:textSize="24sp" /> android:textSize="24sp" />
<TextView <TextView
android:id="@+id/subtitleText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
@ -47,13 +48,14 @@
tools:listitem="@layout/login_chooser_item" /> tools:listitem="@layout/login_chooser_item" />
<TextView <TextView
android:id="@+id/footnoteText"
style="@style/TextAppearance.AppCompat.Small" style="@style/TextAppearance.AppCompat.Small"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
android:text="@string/login_copyright_notice" android:text="@string/login_copyright_notice"
android:textAlignment="center" /> android:gravity="center" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-10-18.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<ImageView
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="48dp"
android:adjustViewBounds="true"
android:scaleType="fitXY"
tools:src="@tools:sample/avatars" />
</FrameLayout>
</layout>

View File

@ -18,6 +18,24 @@
<action <action
android:id="@+id/action_loginChooserFragment_to_loginFormFragment" android:id="@+id/action_loginChooserFragment_to_loginFormFragment"
app:destination="@id/loginFormFragment" /> app:destination="@id/loginFormFragment" />
<!-- eggs -->
<action
android:id="@+id/action_loginChooserFragment_to_loginEggsFragment"
app:destination="@id/loginEggsFragment" />
</fragment>
<!-- eggs -->
<fragment
android:id="@+id/loginEggsFragment"
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginEggsFragment"
android:label="LoginEggsFragment">
<action
android:id="@+id/action_loginEggsFragment_to_loginPrizeFragment"
app:destination="@id/loginPrizeFragment" />
</fragment>
<fragment
android:id="@+id/loginPrizeFragment"
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginPrizeFragment"
android:label="LoginPrizeFragment">
</fragment> </fragment>
<fragment <fragment
android:id="@+id/loginPlatformListFragment" android:id="@+id/loginPlatformListFragment"

View File

@ -1385,4 +1385,5 @@
<string name="login_chooser_mode_recommended">{cmd-information-outline} Zalecane</string> <string name="login_chooser_mode_recommended">{cmd-information-outline} Zalecane</string>
<string name="login_chooser_mode_testing">{cmd-alert-circle-outline} Wersja testowa</string> <string name="login_chooser_mode_testing">{cmd-alert-circle-outline} Wersja testowa</string>
<string name="login_chooser_mode_dev_only">{cmd-android-studio} Wersja deweloperska</string> <string name="login_chooser_mode_dev_only">{cmd-android-studio} Wersja deweloperska</string>
<string name="eggs">\???</string>
</resources> </resources>