From ed3a245b51abb307ee9d1542bd933dcd26031946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 18 Oct 2020 22:14:41 +0200 Subject: [PATCH] [UI/Login] Add new easter eggs. --- .../ui/modules/login/LoginActivity.kt | 10 +- .../ui/modules/login/LoginChooserFragment.kt | 110 ++++++++++++++++- .../ui/modules/login/LoginEggsFragment.kt | 116 ++++++++++++++++++ .../ui/modules/login/LoginFormFragment.kt | 9 +- .../ui/modules/login/LoginPrizeFragment.kt | 77 ++++++++++++ .../res/layout/login_chooser_fragment.xml | 4 +- .../main/res/layout/login_prize_fragment.xml | 24 ++++ app/src/main/res/navigation/nav_login.xml | 18 +++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt create mode 100644 app/src/main/res/layout/login_prize_fragment.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt index c686b857..925b2a98 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt @@ -25,7 +25,6 @@ import kotlin.coroutines.CoroutineContext class LoginActivity : AppCompatActivity(), CoroutineScope { companion object { private const val TAG = "LoginActivity" - var thisOneIsTricky = 0 } private val app: App by lazy { applicationContext as App } @@ -42,6 +41,8 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { val profiles = mutableListOf() val loginStores = mutableListOf() + fun getRootView() = b.root + override fun onBackPressed() { val destination = nav.currentDestination ?: run { nav.navigateUp() @@ -55,6 +56,11 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { return if (destination.id == R.id.loginFinishFragment) return + // eggs + if (destination.id == R.id.loginPrizeFragment) { + finish() + return + } if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) { setResult(Activity.RESULT_CANCELED) finish() @@ -79,8 +85,6 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { super.onCreate(savedInstanceState) setTheme(R.style.AppTheme_Light) - thisOneIsTricky = -1 - navOptions = NavOptions.Builder() .setEnterAnim(R.anim.slide_in_right) .setExitAnim(R.anim.slide_out_left) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt index 8a1045af..ac1bdd3b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt @@ -4,12 +4,18 @@ package pl.szczodrzynski.edziennik.ui.modules.login +import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent +import android.graphics.Color 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 androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager @@ -27,6 +33,8 @@ import kotlin.coroutines.CoroutineContext class LoginChooserFragment : Fragment(), CoroutineScope { companion object { private const val TAG = "LoginChooserFragment" + // eggs + var isRotated = false } private lateinit var app: App @@ -48,13 +56,33 @@ class LoginChooserFragment : Fragment(), CoroutineScope { return b.root } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { if (!isAdded) return val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked) LoginInfo.chooserList = LoginInfo.chooserList - ?: LoginInfo.list.toMutableList() + ?: 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!! b.list.adapter = adapter @@ -68,6 +96,81 @@ class LoginChooserFragment : Fragment(), CoroutineScope { 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 { activity.loginStores.isNotEmpty() -> { // we are navigated here from LoginSummary @@ -93,6 +196,11 @@ class LoginChooserFragment : Fragment(), CoroutineScope { loginType: LoginInfo.Register, loginMode: LoginInfo.Mode ) { + if (loginType.internalName == "eggs") { + nav.navigate(R.id.loginEggsFragment, null, activity.navOptions) + return + } + launch { if (!checkAvailability(loginType.loginType)) return@launch diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt new file mode 100644 index 00000000..0c11bb5a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt @@ -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) {} + } + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt index 82e4f895..e223d5fc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt @@ -32,6 +32,8 @@ import kotlin.coroutines.CoroutineContext class LoginFormFragment : Fragment(), CoroutineScope { companion object { private const val TAG = "LoginFormFragment" + // eggs + var wantEggs = false } private lateinit var app: App @@ -108,8 +110,13 @@ class LoginFormFragment : Fragment(), CoroutineScope { if (credential is LoginInfo.FormCheckbox) { val b = LoginFormCheckboxItemBinding.inflate(layoutInflater) b.checkbox.text = app.getString(credential.name) - b.checkbox.onChange { _, _ -> + b.checkbox.onChange { _, isChecked -> b.errorText.text = null + + // eggs + if (register.internalName == "podlasie") { + wantEggs = !isChecked + } } if (arguments?.containsKey(credential.keyName) == true) { b.checkbox.isChecked = arguments?.getBoolean(credential.keyName) == true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt new file mode 100644 index 00000000..b9fdf43a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt @@ -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() + } + } +} diff --git a/app/src/main/res/layout/login_chooser_fragment.xml b/app/src/main/res/layout/login_chooser_fragment.xml index a35250fb..27e6f309 100644 --- a/app/src/main/res/layout/login_chooser_fragment.xml +++ b/app/src/main/res/layout/login_chooser_fragment.xml @@ -31,6 +31,7 @@ android:textSize="24sp" /> + android:gravity="center" /> + + + + + + + + + diff --git a/app/src/main/res/navigation/nav_login.xml b/app/src/main/res/navigation/nav_login.xml index 7feb8fc7..6fb5e503 100644 --- a/app/src/main/res/navigation/nav_login.xml +++ b/app/src/main/res/navigation/nav_login.xml @@ -18,6 +18,24 @@ + + + + + + + + {cmd-information-outline} Zalecane {cmd-alert-circle-outline} Wersja testowa {cmd-android-studio} Wersja deweloperska + \???