From 5d7695aa154f294defa17e151d88df0b12c60098 Mon Sep 17 00:00:00 2001 From: Minteck Date: Tue, 3 Jan 2023 22:01:57 +0100 Subject: Initial commit --- .../main/java/dev/equestria/delta/HTTPRequest.kt | 61 +++ .../main/java/dev/equestria/delta/MainActivity.kt | 510 +++++++++++++++++++++ 2 files changed, 571 insertions(+) create mode 100644 app/src/main/java/dev/equestria/delta/HTTPRequest.kt create mode 100644 app/src/main/java/dev/equestria/delta/MainActivity.kt (limited to 'app/src/main/java') diff --git a/app/src/main/java/dev/equestria/delta/HTTPRequest.kt b/app/src/main/java/dev/equestria/delta/HTTPRequest.kt new file mode 100644 index 0000000..1bebbc0 --- /dev/null +++ b/app/src/main/java/dev/equestria/delta/HTTPRequest.kt @@ -0,0 +1,61 @@ +package dev.equestria.delta + +import android.content.Context +import android.content.res.Resources +import android.util.Log +import com.android.volley.Request +import com.android.volley.toolbox.JsonObjectRequest +import com.android.volley.toolbox.Volley +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dev.equestria.delta.databinding.ActivityMainBinding +import org.json.JSONObject +import kotlin.system.exitProcess + +class HTTPRequest { + companion object { + fun request( + url: String, + session: String?, + context: Context, + resources: Resources, + activity: MainActivity, + initial: Boolean, + binding: ActivityMainBinding, + positiveCallback: (JSONObject) -> Unit = { }, + negativeCallback: (Unit) -> Unit = { } + ) { + val volleyQueue = Volley.newRequestQueue(context) + + val data = JSONObject() + data.put("session", session) + + val jsonObjectRequest = JsonObjectRequest(Request.Method.POST, url, data, + + { response -> + Log.i("HTTPRequest", response.toString()) + positiveCallback(response) + }, + + { error -> + MaterialAlertDialogBuilder(context).setCancelable(false) + .setTitle(resources.getString(R.string.offline_title)) + .setMessage(resources.getString(R.string.offline_message)) + .setPositiveButton(resources.getString(R.string.offline_retry)) { _, _ -> + if (initial) { + activity.initialCheck() + } else { + binding.webView.reload() + } + }.setNegativeButton(resources.getString(R.string.offline_close)) { _, _ -> + activity.moveTaskToBack(true) + exitProcess(0) + }.setNeutralButton(resources.getString(R.string.offline_status)) { _, _ -> + negativeCallback(Unit) + }.show() + Log.e("HTTPRequest", "Request error: ${error.localizedMessage}") + }) + + volleyQueue.add(jsonObjectRequest) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/equestria/delta/MainActivity.kt b/app/src/main/java/dev/equestria/delta/MainActivity.kt new file mode 100644 index 0000000..7e239c6 --- /dev/null +++ b/app/src/main/java/dev/equestria/delta/MainActivity.kt @@ -0,0 +1,510 @@ +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" + ) { + 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 + } +} \ No newline at end of file -- cgit