package dev.equestria.delta import android.Manifest import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager import android.content.Intent import android.content.res.Resources.getSystem import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.os.Bundle import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.webkit.CookieManager import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import com.google.android.gms.tasks.OnCompleteListener import com.google.android.material.color.DynamicColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.elevation.SurfaceColors import com.google.firebase.messaging.FirebaseMessaging import com.squareup.picasso.Picasso import com.squareup.picasso.Target import dev.equestria.delta.databinding.ActivityMainBinding import org.json.JSONObject import java.net.URL import kotlin.system.exitProcess val Int.dp: Int get() = (this / getSystem().displayMetrics.density).toInt() class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var appMenu: Menu private lateinit var navigationMenu: Menu private var changedNavItem: Boolean = false private var deltaInformation: JSONObject? = null private var lastRequestLoggedIn: Boolean = false override fun onCreateOptionsMenu(menu: Menu?): Boolean { val inflater = menuInflater inflater.inflate(R.menu.action_bar, menu) if (menu != null) { appMenu = menu } return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.user -> { binding.webView.loadUrl("${getString(R.string.delta_root)}/profile") return true } R.id.btn_forward -> { if (binding.webView.canGoForward()) binding.webView.goForward() return true } R.id.btn_reload -> { binding.webView.reload() return true } R.id.btn_user_logout -> { binding.webView.loadUrl("${getString(R.string.delta_root)}/logout") return true } R.id.btn_about -> { MaterialAlertDialogBuilder(binding.root.context).setTitle( getString( R.string.about_title, getString(R.string.app_launch_name) ) ).setMessage( getString( R.string.about_message, BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ", " + BuildConfig.BUILD_TYPE + ")", deltaInformation?.get("version") ?: "-", Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME + " (" + Build.VERSION.SDK_INT + ")", System.getProperty("os.version"), Build.DEVICE + "/" + Build.MODEL, Build.VERSION.SECURITY_PATCH, Build.BOARD, Build.BOOTLOADER ) ).setPositiveButton(getString(R.string.about_close)) { _, _ -> }.show() return true } else -> return false } } fun getCookie(siteName: String?, CookieName: String?): String? { var cookieValue: String? = null val cookieManager: CookieManager = CookieManager.getInstance() val cookies: String? = cookieManager.getCookie(siteName) return if (cookies != null) { val temp = cookies.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() for (ar1 in temp) { if (ar1.contains(CookieName!!)) { val temp1 = ar1.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() cookieValue = temp1[1] } } cookieValue } else { "" } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) DynamicColors.applyToActivitiesIfAvailable(application) val name = getString(R.string.channel_name) val descriptionText = getString(R.string.channel_description) val importance = NotificationManager.IMPORTANCE_DEFAULT val mChannel = NotificationChannel("default", name, importance) mChannel.description = descriptionText val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(mChannel) if (Build.VERSION.SDK_INT >= 33) { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1 ) } FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> if (!task.isSuccessful) { Log.w("Firebase", "Fetching FCM registration token failed", task.exception) return@OnCompleteListener } val token = task.result Log.d("Firebase", token) HTTPRequest.request( "${getString(R.string.delta_root)}/handoff/fcm/", getCookie(getString(R.string.delta_root), "DeltaSession") + "||||" + token, binding.root.context, resources, this, true, binding ) { val openURL = Intent(Intent.ACTION_VIEW) openURL.data = Uri.parse("https://status.equestria.dev/") startActivity(openURL) } }) val color = SurfaceColors.SURFACE_2.getColor(this) window.statusBarColor = color window.navigationBarColor = color supportActionBar?.setDisplayHomeAsUpEnabled(true) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) navigationMenu = binding.navigationBar.menu initialCheck() } fun initialCheck() { HTTPRequest.request("${getString(R.string.delta_root)}/handoff/version/", getCookie(getString(R.string.delta_root), "DeltaSession"), binding.root.context, resources, this, true, binding, { deltaInformation = it appMenu.getItem(3).isEnabled = deltaInformation!!.get("loggedIn") as Boolean initialise() }) { val openURL = Intent(Intent.ACTION_VIEW) openURL.data = Uri.parse("https://status.equestria.dev/") startActivity(openURL) } } fun routineCheck() { HTTPRequest.request("${getString(R.string.delta_root)}/handoff/version/", getCookie(getString(R.string.delta_root), "DeltaSession"), binding.root.context, resources, this, false, binding, { deltaInformation = it appMenu.getItem(3).isEnabled = deltaInformation!!.get("loggedIn") as Boolean if (deltaInformation!!.get("loggedIn") as Boolean || lastRequestLoggedIn) { refreshAvatar(deltaInformation!!) lastRequestLoggedIn = deltaInformation!!.get("loggedIn") as Boolean if (lastRequestLoggedIn) { appMenu.getItem(0).title = deltaInformation!!.get("name") as CharSequence? } else { appMenu.getItem(0).title = getString(R.string.navigation_profile) } } }) { val openURL = Intent(Intent.ACTION_VIEW) openURL.data = Uri.parse("https://status.equestria.dev/") startActivity(openURL) } } fun refreshAvatar(deltaInformation: JSONObject) { val target = object : Target { override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) { try { if (bitmap != null) { appMenu.getItem(0).icon = BitmapDrawable(resources, bitmap) } } catch (ex: IllegalArgumentException) { Log.e("Picasso", ex.toString()) } } override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { Log.e("Picasso", e.toString()) } override fun onPrepareLoad(placeHolderDrawable: Drawable?) {} } Picasso.get().load( "${getString(R.string.delta_root)}/handoff/avatar/?token=" + deltaInformation.get("session") ).into(target) } @SuppressLint("SetJavaScriptEnabled") fun initialise() { binding.webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { appMenu.getItem(2).isEnabled = true binding.progressBar.visibility = View.INVISIBLE binding.webView.visibility = View.VISIBLE val pageTitle = binding.webView.title?.split("ยท")?.get(0)?.trim() ?: "" if (pageTitle.startsWith("(") && pageTitle.split(") ").count() > 1) { title = pageTitle.split(")")[1].trim() binding.navigationBar.getOrCreateBadge(R.id.navigation_profile).isVisible = true } else { title = pageTitle binding.navigationBar.getOrCreateBadge(R.id.navigation_profile).isVisible = false } if (URL(url).path.startsWith("/login")) { binding.navigationBar.visibility = View.GONE val params = binding.webView.layoutParams as ViewGroup.MarginLayoutParams params.setMargins(0, 0, 0, 80.dp) supportActionBar?.hide() val color = SurfaceColors.SURFACE_0.getColor(binding.root.context) window.statusBarColor = color window.navigationBarColor = color } else { val params = binding.webView.layoutParams as ViewGroup.MarginLayoutParams params.setMargins(0, 0, 0, 0) binding.navigationBar.visibility = View.VISIBLE supportActionBar?.show() val color = SurfaceColors.SURFACE_2.getColor(binding.root.context) window.statusBarColor = color window.navigationBarColor = color } routineCheck() appMenu.getItem(1).isEnabled = binding.webView.canGoForward() } override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { appMenu.getItem(2).isEnabled = false binding.progressBar.visibility = View.VISIBLE if (!changedNavItem) { if (URL(url).path.startsWith("/articles/") || URL(url).path === "/articles" || URL( url ).path.startsWith("/edit/") || URL(url).path === "/edit" || URL(url).path.startsWith( "/request/" ) || URL(url).path === "/request" || URL(url).path.startsWith("/people/") || URL( url ).path === "/people" || URL(url).path.startsWith("/gallery/") || URL(url).path === "/gallery" ) { changedNavItem = true binding.navigationBar.selectedItemId = R.id.navigation_content } else if (URL(url).path.startsWith("/search/") || URL(url).path === "/search") { changedNavItem = true binding.navigationBar.selectedItemId = R.id.navigation_search } else if (URL(url).path.startsWith("/admin/") || URL(url).path === "/admin" || URL( url ).path.startsWith("/profile/") || URL(url).path === "/profile" || URL(url).path.startsWith( "/requests/" ) || URL(url).path === "/requests" || URL(url).path.startsWith("/plus/") || URL( url ).path === "/plus" || URL(url).path.startsWith("/alerts/") || URL(url).path === "/alerts" || URL( url ).path.startsWith("/support/") || URL(url).path === "/support" || URL(url).path.startsWith( "/mobile_profile/" ) || URL(url).path === "/mobile_profile" || URL(url).path.startsWith( "/beta/" ) || URL(url).path === "/beta" || URL(url).path.startsWith( "/studio/" ) || URL(url).path === "/studio" || URL(url).path.startsWith( "/logout/" ) || URL(url).path === "/logout" ) { changedNavItem = true binding.navigationBar.selectedItemId = R.id.navigation_profile } else { changedNavItem = true binding.navigationBar.selectedItemId = R.id.navigation_dashboard } } appMenu.getItem(1).isEnabled = binding.webView.canGoForward() changedNavItem = false } override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest ): WebResourceResponse? { request.url.host?.let { Log.i("MainActivity", '"' + it + '"') } if (request.url.host != getString(R.string.delta_root).split("/")[2]) { val openURL = Intent(Intent.ACTION_VIEW) openURL.data = request.url startActivity(openURL) return WebResourceResponse("text/javascript", "UTF-8", null) } return null } } binding.navigationBar.setOnItemSelectedListener { item -> when (item.itemId) { R.id.navigation_dashboard -> { setNavigationBarSelected(item.itemId) if (!changedNavItem) { changedNavItem = true binding.webView.loadUrl("${getString(R.string.delta_root)}/") } true } R.id.navigation_search -> { setNavigationBarSelected(item.itemId) if (!changedNavItem) { changedNavItem = true binding.webView.loadUrl("${getString(R.string.delta_root)}/search") } true } R.id.navigation_content -> { setNavigationBarSelected(item.itemId) if (!changedNavItem) { changedNavItem = true binding.webView.loadUrl("${getString(R.string.delta_root)}/content") } true } R.id.navigation_profile -> { setNavigationBarSelected(item.itemId) if (!changedNavItem) { changedNavItem = true binding.webView.loadUrl("${getString(R.string.delta_root)}/profile") } true } else -> false } } binding.navigationBar.menu.findItem(R.id.navigation_profile).iconTintList = null binding.webView.settings.javaScriptEnabled = true handoffApi31() } fun setNavigationBarSelected(id: Int) { binding.navigationBar.menu.findItem(R.id.navigation_dashboard) .setIcon(R.drawable.outline_home_24) binding.navigationBar.menu.findItem(R.id.navigation_content) .setIcon(R.drawable.outline_text_snippet_24) binding.navigationBar.menu.findItem(R.id.navigation_search) .setIcon(R.drawable.outline_search_24) binding.navigationBar.menu.findItem(R.id.navigation_profile) .setIcon(R.drawable.outline_person_24) when (id) { R.id.navigation_dashboard -> { binding.navigationBar.menu.findItem(R.id.navigation_dashboard) .setIcon(R.drawable.baseline_home_24) } R.id.navigation_content -> { binding.navigationBar.menu.findItem(R.id.navigation_content) .setIcon(R.drawable.baseline_text_snippet_24) } R.id.navigation_search -> { binding.navigationBar.menu.findItem(R.id.navigation_search) .setIcon(R.drawable.baseline_search_24) } R.id.navigation_profile -> { binding.navigationBar.menu.findItem(R.id.navigation_profile) .setIcon(R.drawable.baseline_person_24) } else -> {} } } fun handoffApi31() { val colors: ArrayList = arrayListOf( SurfaceColors.SURFACE_0.getColor(this), SurfaceColors.SURFACE_1.getColor(this), SurfaceColors.SURFACE_2.getColor(this), SurfaceColors.SURFACE_3.getColor(this), SurfaceColors.SURFACE_4.getColor(this), SurfaceColors.SURFACE_5.getColor(this), binding.textView.currentTextColor, binding.textView.currentHintTextColor, binding.button.currentTextColor, binding.button.highlightColor ) val appLinkAction = intent.action val appLinkData: Uri? = intent.data if (Intent.ACTION_VIEW == appLinkAction) { if (appLinkData != null) { binding.webView.loadUrl( "${getString(R.string.delta_root)}/handoff/?version=" + BuildConfig.VERSION_CODE + "&colors=" + colors.joinToString( "," ) + "&return=" + java.net.URLEncoder.encode(appLinkData.path, "utf-8") ) } else { binding.webView.loadUrl( "${getString(R.string.delta_root)}/handoff/?version=" + BuildConfig.VERSION_CODE + "&colors=" + colors.joinToString( "," ) + "&return=/" ) } } else { binding.webView.loadUrl( "${getString(R.string.delta_root)}/handoff/?version=" + BuildConfig.VERSION_CODE + "&colors=" + colors.joinToString( "," ) + "&return=/" ) } } @Deprecated("Deprecated in Java") override fun onBackPressed() { if (binding.webView.canGoBack()) { binding.webView.goBack() } else { moveTaskToBack(true) exitProcess(0) } } override fun onSupportNavigateUp(): Boolean { if (binding.webView.canGoBack()) { binding.webView.goBack() } else { moveTaskToBack(true) exitProcess(0) } return true } }