i give up im just pushing everything
This commit is contained in:
parent
0e48db5921
commit
f182a98b69
20 changed files with 519 additions and 407 deletions
|
@ -3,11 +3,11 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'dev.vencord.vendroid'
|
namespace 'com.nin0dev.vendroid'
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "dev.vencord.vendroid"
|
applicationId "com.nin0dev.vendroid"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 3
|
versionCode 3
|
||||||
|
@ -16,7 +16,7 @@ android {
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".dbg"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
|
@ -32,5 +32,6 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.annotation:annotation:1.7.1'
|
implementation 'androidx.annotation:annotation:1.7.1'
|
||||||
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28'
|
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28'
|
||||||
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,28 +5,34 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/LoadingTheme"
|
android:theme="@style/LoadingTheme"
|
||||||
tools:targetApi="34"
|
tools:targetApi="34">
|
||||||
>
|
<activity
|
||||||
|
android:name=".WelcomeActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.Material3.DayNight.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection"
|
android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection"
|
||||||
|
android:exported="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/LoadingTheme">
|
android:theme="@style/LoadingTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
<data android:host="discord.com" />
|
<data android:host="discord.com" />
|
||||||
|
|
5
app/src/main/java/com/nin0dev/vendroid/Constants.kt
Normal file
5
app/src/main/java/com/nin0dev/vendroid/Constants.kt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val JS_BUNDLE_URL = "https://github.com/VendroidEnhanced/Vencord/releases/latest/download/mobile.js"
|
||||||
|
}
|
71
app/src/main/java/com/nin0dev/vendroid/HttpClient.kt
Normal file
71
app/src/main/java/com/nin0dev/vendroid/HttpClient.kt
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
object HttpClient {
|
||||||
|
@JvmField
|
||||||
|
var VencordRuntime: String? = null
|
||||||
|
@JvmField
|
||||||
|
var VencordMobileRuntime: String? = null
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun fetchVencord(activity: Activity) {
|
||||||
|
if (VencordRuntime != null) return
|
||||||
|
val res = activity.resources
|
||||||
|
res.openRawResource(R.raw.vencord_mobile).use { `is` -> VencordMobileRuntime = readAsText(`is`) }
|
||||||
|
val conn = fetch(Constants.JS_BUNDLE_URL)
|
||||||
|
conn.inputStream.use { `is` -> VencordRuntime = readAsText(`is`) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun fetch(url: String): HttpURLConnection {
|
||||||
|
val conn = URL(url).openConnection() as HttpURLConnection
|
||||||
|
if (conn.getResponseCode() >= 300) {
|
||||||
|
throw HttpException(conn)
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun readAsText(`is`: InputStream): String {
|
||||||
|
ByteArrayOutputStream().use { baos ->
|
||||||
|
var n: Int
|
||||||
|
val buf = ByteArray(16384) // 16 KB
|
||||||
|
while (`is`.read(buf).also { n = it } > -1) {
|
||||||
|
baos.write(buf, 0, n)
|
||||||
|
}
|
||||||
|
baos.flush()
|
||||||
|
return baos.toString("UTF-8")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpException(private val conn: HttpURLConnection) : IOException() {
|
||||||
|
override var message: String? = null
|
||||||
|
get() {
|
||||||
|
if (field == null) {
|
||||||
|
try {
|
||||||
|
conn.errorStream.use { es ->
|
||||||
|
field = String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
"%d: %s (%s)\n%s",
|
||||||
|
conn.getResponseCode(),
|
||||||
|
conn.getResponseMessage(),
|
||||||
|
conn.url.toString(),
|
||||||
|
readAsText(es)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
field = "Error while building message lmao. Url is " + conn.url.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
}
|
||||||
|
}
|
31
app/src/main/java/com/nin0dev/vendroid/Logger.kt
Normal file
31
app/src/main/java/com/nin0dev/vendroid/Logger.kt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
object Logger {
|
||||||
|
private const val TAG = "Vencord"
|
||||||
|
@JvmStatic
|
||||||
|
fun e(message: String?) {
|
||||||
|
Log.e(TAG, message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun e(message: String?, e: Throwable?) {
|
||||||
|
Log.e(TAG, message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun w(message: String?) {
|
||||||
|
Log.w(TAG, message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun i(message: String?) {
|
||||||
|
Log.i(TAG, message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun d(message: String?) {
|
||||||
|
Log.d(TAG, message!!)
|
||||||
|
}
|
||||||
|
}
|
115
app/src/main/java/com/nin0dev/vendroid/MainActivity.kt
Normal file
115
app/src/main/java/com/nin0dev/vendroid/MainActivity.kt
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.StrictMode
|
||||||
|
import android.os.StrictMode.ThreadPolicy
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.webkit.ValueCallback
|
||||||
|
import android.webkit.WebView
|
||||||
|
import com.nin0dev.vendroid.HttpClient.fetchVencord
|
||||||
|
import com.nin0dev.vendroid.Logger.e
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class MainActivity : Activity() {
|
||||||
|
private var wvInitialized = false
|
||||||
|
private var wv: WebView? = null
|
||||||
|
@JvmField
|
||||||
|
var filePathCallback: ValueCallback<Array<Uri?>?>? = null
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled") // mad? watch this swag
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
startActivity(Intent(this@MainActivity, WelcomeActivity::class.java))
|
||||||
|
// https://developer.chrome.com/docs/devtools/remote-debugging/webviews/
|
||||||
|
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
wv = findViewById(R.id.webview)!!
|
||||||
|
explodeAndroid()
|
||||||
|
wv!!.setWebViewClient(VWebviewClient())
|
||||||
|
wv!!.setWebChromeClient(VChromeClient(this))
|
||||||
|
val s = wv?.getSettings()!!
|
||||||
|
s.javaScriptEnabled = true
|
||||||
|
s.domStorageEnabled = true
|
||||||
|
s.allowFileAccess = true
|
||||||
|
wv?.addJavascriptInterface(VencordNative(this, wv!!), "VencordMobileNative")
|
||||||
|
try {
|
||||||
|
fetchVencord(this)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
|
||||||
|
}
|
||||||
|
val intent = intent
|
||||||
|
if (intent.action == Intent.ACTION_VIEW) {
|
||||||
|
val data = intent.data
|
||||||
|
if (data != null) handleUrl(intent.data)
|
||||||
|
} else {
|
||||||
|
wv!!.loadUrl("https://discord.com/app")
|
||||||
|
}
|
||||||
|
wvInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK && wv != null) {
|
||||||
|
runOnUiThread { wv!!.evaluateJavascript("VencordMobile.onBackPress()") { r: String -> if ("false" == r) onBackPressed() } }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent) {
|
||||||
|
if (requestCode != FILECHOOSER_RESULTCODE || filePathCallback == null) return
|
||||||
|
if (resultCode != RESULT_OK || intent == null) {
|
||||||
|
filePathCallback!!.onReceiveValue(null)
|
||||||
|
} else {
|
||||||
|
var uris: Array<Uri?>?
|
||||||
|
try {
|
||||||
|
val clipData = intent.clipData
|
||||||
|
if (clipData != null) { // multiple items
|
||||||
|
uris = arrayOfNulls(clipData.itemCount)
|
||||||
|
for (i in 0 until clipData.itemCount) {
|
||||||
|
uris[i] = clipData.getItemAt(i).uri
|
||||||
|
}
|
||||||
|
} else { // single item
|
||||||
|
uris = arrayOf(intent.data)
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
e("Error during file upload", ex)
|
||||||
|
uris = null
|
||||||
|
}
|
||||||
|
filePathCallback!!.onReceiveValue(uris)
|
||||||
|
}
|
||||||
|
filePathCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun explodeAndroid() {
|
||||||
|
StrictMode.setThreadPolicy(
|
||||||
|
ThreadPolicy.Builder() // trolley
|
||||||
|
.permitNetwork()
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleUrl(url: Uri?) {
|
||||||
|
if (url != null) {
|
||||||
|
if (url.authority != "discord.com") return
|
||||||
|
if (!wvInitialized) {
|
||||||
|
wv!!.loadUrl(url.toString())
|
||||||
|
} else {
|
||||||
|
wv!!.evaluateJavascript("Vencord.Webpack.Common.NavigationRouter.transitionTo(\"" + url.path + "\")", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
val data = intent.data
|
||||||
|
data?.let { handleUrl(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FILECHOOSER_RESULTCODE = 8485
|
||||||
|
}
|
||||||
|
}
|
40
app/src/main/java/com/nin0dev/vendroid/VChromeClient.kt
Normal file
40
app/src/main/java/com/nin0dev/vendroid/VChromeClient.kt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.ConsoleMessage
|
||||||
|
import android.webkit.ConsoleMessage.MessageLevel
|
||||||
|
import android.webkit.ValueCallback
|
||||||
|
import android.webkit.WebChromeClient
|
||||||
|
import android.webkit.WebView
|
||||||
|
import com.nin0dev.vendroid.Logger.d
|
||||||
|
import com.nin0dev.vendroid.Logger.e
|
||||||
|
import com.nin0dev.vendroid.Logger.i
|
||||||
|
import com.nin0dev.vendroid.Logger.w
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class VChromeClient(private val activity: MainActivity) : WebChromeClient() {
|
||||||
|
override fun onConsoleMessage(msg: ConsoleMessage): Boolean {
|
||||||
|
val m = String.format(Locale.ENGLISH, "[Javascript] %s @ %d: %s", msg.message(), msg.lineNumber(), msg.sourceId())
|
||||||
|
when (msg.messageLevel()) {
|
||||||
|
MessageLevel.DEBUG -> d(m)
|
||||||
|
MessageLevel.ERROR -> e(m)
|
||||||
|
MessageLevel.WARNING -> w(m)
|
||||||
|
else -> i(m)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
|
||||||
|
if (activity.filePathCallback != null) activity.filePathCallback?.onReceiveValue(null)
|
||||||
|
activity.filePathCallback = filePathCallback as ValueCallback<Array<Uri?>?>
|
||||||
|
val i = fileChooserParams.createIntent()
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(i, MainActivity.FILECHOOSER_RESULTCODE)
|
||||||
|
} catch (ex: ActivityNotFoundException) {
|
||||||
|
activity.filePathCallback = null
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
68
app/src/main/java/com/nin0dev/vendroid/VWebviewClient.kt
Normal file
68
app/src/main/java/com/nin0dev/vendroid/VWebviewClient.kt
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import com.nin0dev.vendroid.Logger.e
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class VWebviewClient : WebViewClient() {
|
||||||
|
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||||
|
val url = request.url
|
||||||
|
if ("discord.com" == url.authority || "about:blank" == url.toString()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, url)
|
||||||
|
view.context.startActivity(intent)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
||||||
|
view.evaluateJavascript(HttpClient.VencordRuntime!!, null)
|
||||||
|
view.evaluateJavascript(HttpClient.VencordMobileRuntime!!, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
view.visibility = View.VISIBLE
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldInterceptRequest(view: WebView, req: WebResourceRequest): WebResourceResponse? {
|
||||||
|
val uri = req.url
|
||||||
|
if (req.isForMainFrame || req.url.path!!.endsWith(".css")) {
|
||||||
|
try {
|
||||||
|
return doFetch(req)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
e("Error during shouldInterceptRequest", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun doFetch(req: WebResourceRequest): WebResourceResponse {
|
||||||
|
val url = req.url.toString()
|
||||||
|
val conn = URL(url).openConnection() as HttpURLConnection
|
||||||
|
conn.setRequestMethod(req.method)
|
||||||
|
for ((key, value) in req.requestHeaders) {
|
||||||
|
conn.setRequestProperty(key, value)
|
||||||
|
}
|
||||||
|
val code = conn.getResponseCode()
|
||||||
|
val msg = conn.getResponseMessage()
|
||||||
|
val headers = conn.headerFields
|
||||||
|
val modifiedHeaders = HashMap<String, String>(headers.size)
|
||||||
|
for ((key, value) in headers) {
|
||||||
|
if (!"Content-Security-Policy".equals(key, ignoreCase = true)) {
|
||||||
|
modifiedHeaders[key] = value[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (url.endsWith(".css")) modifiedHeaders["Content-Type"] = "text/css"
|
||||||
|
return WebResourceResponse(modifiedHeaders.getOrDefault("Content-Type", "application/octet-stream"), "utf-8", code, msg, modifiedHeaders, conn.inputStream)
|
||||||
|
}
|
||||||
|
}
|
15
app/src/main/java/com/nin0dev/vendroid/VencordNative.kt
Normal file
15
app/src/main/java/com/nin0dev/vendroid/VencordNative.kt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.webkit.JavascriptInterface
|
||||||
|
import android.webkit.WebView
|
||||||
|
|
||||||
|
class VencordNative(private val activity: Activity, private val wv: WebView) {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun goBack() {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
if (wv.canGoBack()) wv.goBack() else // no idea what i was smoking when I wrote this
|
||||||
|
activity.getActionBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
app/src/main/java/com/nin0dev/vendroid/WelcomeActivity.kt
Normal file
11
app/src/main/java/com/nin0dev/vendroid/WelcomeActivity.kt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package com.nin0dev.vendroid
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
class WelcomeActivity : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_welcome)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
public class Constants {
|
|
||||||
public static final String JS_BUNDLE_URL = "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js";
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class HttpClient {
|
|
||||||
public static final class HttpException extends IOException {
|
|
||||||
private final HttpURLConnection conn;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public HttpException(HttpURLConnection conn) {
|
|
||||||
this.conn = conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public String getMessage() {
|
|
||||||
if (message == null) {
|
|
||||||
try(var es = conn.getErrorStream()) {
|
|
||||||
message = String.format(
|
|
||||||
Locale.ENGLISH,
|
|
||||||
"%d: %s (%s)\n%s",
|
|
||||||
conn.getResponseCode(),
|
|
||||||
conn.getResponseMessage(),
|
|
||||||
conn.getURL().toString(),
|
|
||||||
readAsText(es)
|
|
||||||
);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
message = "Error while building message lmao. Url is " + conn.getURL().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String VencordRuntime;
|
|
||||||
public static String VencordMobileRuntime;
|
|
||||||
|
|
||||||
public static void fetchVencord(Activity activity) throws IOException {
|
|
||||||
if (VencordRuntime != null) return;
|
|
||||||
|
|
||||||
var res = activity.getResources();
|
|
||||||
try (var is = res.openRawResource(R.raw.vencord_mobile)) {
|
|
||||||
VencordMobileRuntime = readAsText(is);
|
|
||||||
}
|
|
||||||
|
|
||||||
var conn = fetch(Constants.JS_BUNDLE_URL);
|
|
||||||
try (var is = conn.getInputStream()) {
|
|
||||||
VencordRuntime = readAsText(is);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpURLConnection fetch(String url) throws IOException {
|
|
||||||
var conn = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
if (conn.getResponseCode() >= 300) {
|
|
||||||
throw new HttpException(conn);
|
|
||||||
}
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String readAsText(InputStream is) throws IOException {
|
|
||||||
try (var baos = new ByteArrayOutputStream()) {
|
|
||||||
int n;
|
|
||||||
byte[] buf = new byte[16384]; // 16 KB
|
|
||||||
while ((n = is.read(buf)) > -1) {
|
|
||||||
baos.write(buf, 0, n);
|
|
||||||
}
|
|
||||||
baos.flush();
|
|
||||||
|
|
||||||
//noinspection CharsetObjectCanBeUsed thank you so much android studio but no i do not want to use an sdk33 api ._.
|
|
||||||
return baos.toString("UTF-8");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public final class Logger {
|
|
||||||
private static final String TAG = "Vencord";
|
|
||||||
|
|
||||||
public static void e(String message) {
|
|
||||||
Log.e(TAG, message);
|
|
||||||
}
|
|
||||||
public static void e(String message, Throwable e) {
|
|
||||||
Log.e(TAG, message, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void w(String message) {
|
|
||||||
Log.w(TAG, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void i(String message) {
|
|
||||||
Log.i(TAG, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void d(String message) {
|
|
||||||
Log.d(TAG, message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.StrictMode;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.webkit.ValueCallback;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
|
||||||
public static final int FILECHOOSER_RESULTCODE = 8485;
|
|
||||||
private boolean wvInitialized = false;
|
|
||||||
private WebView wv;
|
|
||||||
|
|
||||||
public ValueCallback<Uri[]> filePathCallback;
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled") // mad? watch this swag
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
// https://developer.chrome.com/docs/devtools/remote-debugging/webviews/
|
|
||||||
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
|
|
||||||
wv = findViewById(R.id.webview);
|
|
||||||
|
|
||||||
explodeAndroid();
|
|
||||||
|
|
||||||
wv.setWebViewClient(new VWebviewClient());
|
|
||||||
wv.setWebChromeClient(new VChromeClient(this));
|
|
||||||
|
|
||||||
var s = wv.getSettings();
|
|
||||||
s.setJavaScriptEnabled(true);
|
|
||||||
s.setDomStorageEnabled(true);
|
|
||||||
s.setAllowFileAccess(true);
|
|
||||||
|
|
||||||
wv.addJavascriptInterface(new VencordNative(this, wv), "VencordMobileNative");
|
|
||||||
|
|
||||||
try {
|
|
||||||
HttpClient.fetchVencord(this);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.e("Failed to fetch Vencord", ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (Objects.equals(intent.getAction(), Intent.ACTION_VIEW)) {
|
|
||||||
Uri data = intent.getData();
|
|
||||||
if (data != null) handleUrl(intent.getData());
|
|
||||||
} else {
|
|
||||||
wv.loadUrl("https://discord.com/app");
|
|
||||||
}
|
|
||||||
|
|
||||||
wvInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK && wv != null) {
|
|
||||||
runOnUiThread(() -> wv.evaluateJavascript("VencordMobile.onBackPress()", r -> {
|
|
||||||
if ("false".equals(r))
|
|
||||||
this.onBackPressed ();
|
|
||||||
}));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
||||||
if (requestCode != FILECHOOSER_RESULTCODE || filePathCallback == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (resultCode != RESULT_OK || intent == null) {
|
|
||||||
filePathCallback.onReceiveValue(null);
|
|
||||||
} else {
|
|
||||||
Uri[] uris;
|
|
||||||
try {
|
|
||||||
var clipData = intent.getClipData();
|
|
||||||
if (clipData != null) { // multiple items
|
|
||||||
uris = new Uri[clipData.getItemCount()];
|
|
||||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
|
||||||
uris[i] = clipData.getItemAt(i).getUri();
|
|
||||||
}
|
|
||||||
} else { // single item
|
|
||||||
uris = new Uri[] { intent.getData() };
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.e("Error during file upload", ex);
|
|
||||||
uris = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
filePathCallback.onReceiveValue(uris);
|
|
||||||
}
|
|
||||||
filePathCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void explodeAndroid() {
|
|
||||||
StrictMode.setThreadPolicy(
|
|
||||||
new StrictMode.ThreadPolicy.Builder()
|
|
||||||
// trolley
|
|
||||||
.permitNetwork()
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleUrl(Uri url) {
|
|
||||||
if (url != null) {
|
|
||||||
if (!url.getAuthority().equals("discord.com")) return;
|
|
||||||
if (!wvInitialized) {
|
|
||||||
wv.loadUrl(url.toString());
|
|
||||||
} else {
|
|
||||||
wv.evaluateJavascript("Vencord.Webpack.Common.NavigationRouter.transitionTo(\"" + url.getPath() + "\")", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNewIntent(Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
Uri data = intent.getData();
|
|
||||||
if (data != null) handleUrl(data);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.webkit.*;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class VChromeClient extends WebChromeClient {
|
|
||||||
private final MainActivity activity;
|
|
||||||
|
|
||||||
public VChromeClient(MainActivity activity) {
|
|
||||||
this.activity = activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onConsoleMessage(ConsoleMessage msg) {
|
|
||||||
var m = String.format(Locale.ENGLISH, "[Javascript] %s @ %d: %s", msg.message(), msg.lineNumber(), msg.sourceId());
|
|
||||||
switch (msg.messageLevel()) {
|
|
||||||
case DEBUG:
|
|
||||||
Logger.d(m);
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
Logger.e(m);
|
|
||||||
break;
|
|
||||||
case WARNING:
|
|
||||||
Logger.w(m);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Logger.i(m);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
|
|
||||||
if (activity.filePathCallback != null)
|
|
||||||
activity.filePathCallback.onReceiveValue(null);
|
|
||||||
|
|
||||||
activity.filePathCallback = filePathCallback;
|
|
||||||
|
|
||||||
var i = fileChooserParams.createIntent();
|
|
||||||
try {
|
|
||||||
activity.startActivityForResult(i, MainActivity.FILECHOOSER_RESULTCODE);
|
|
||||||
} catch (ActivityNotFoundException ex) {
|
|
||||||
activity.filePathCallback = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.view.View;
|
|
||||||
import android.webkit.*;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class VWebviewClient extends WebViewClient {
|
|
||||||
@Override
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
|
||||||
var url = request.getUrl();
|
|
||||||
if ("discord.com".equals(url.getAuthority()) || "about:blank".equals(url.toString())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, url);
|
|
||||||
view.getContext().startActivity(intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
|
||||||
view.evaluateJavascript(HttpClient.VencordRuntime, null);
|
|
||||||
view.evaluateJavascript(HttpClient.VencordMobileRuntime, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageFinished(WebView view, String url) {
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
super.onPageFinished(view, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest req) {
|
|
||||||
var uri = req.getUrl();
|
|
||||||
if (req.isForMainFrame() || req.getUrl().getPath().endsWith(".css")) {
|
|
||||||
try {
|
|
||||||
return doFetch(req);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.e("Error during shouldInterceptRequest", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private WebResourceResponse doFetch(WebResourceRequest req) throws IOException {
|
|
||||||
var url = req.getUrl().toString();
|
|
||||||
var conn = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
conn.setRequestMethod(req.getMethod());
|
|
||||||
for (var h : req.getRequestHeaders().entrySet()) {
|
|
||||||
conn.setRequestProperty(h.getKey(), h.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
var code = conn.getResponseCode();
|
|
||||||
var msg = conn.getResponseMessage();
|
|
||||||
|
|
||||||
var headers = conn.getHeaderFields();
|
|
||||||
var modifiedHeaders = new HashMap<String, String>(headers.size());
|
|
||||||
for (var header : headers.entrySet()) {
|
|
||||||
if (!"Content-Security-Policy".equalsIgnoreCase(header.getKey())) {
|
|
||||||
modifiedHeaders.put(header.getKey(), header.getValue().get(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (url.endsWith(".css")) modifiedHeaders.put("Content-Type", "text/css");
|
|
||||||
|
|
||||||
return new WebResourceResponse(modifiedHeaders.getOrDefault("Content-Type", "application/octet-stream"), "utf-8", code, msg, modifiedHeaders, conn.getInputStream());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package dev.vencord.vendroid;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.webkit.JavascriptInterface;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
public class VencordNative {
|
|
||||||
private final WebView wv;
|
|
||||||
private final Activity activity;
|
|
||||||
|
|
||||||
public VencordNative(Activity activity, WebView wv) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.wv = wv;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
public void goBack() {
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
if (wv.canGoBack())
|
|
||||||
wv.goBack();
|
|
||||||
else
|
|
||||||
// no idea what i was smoking when I wrote this
|
|
||||||
activity.getActionBar();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
5
app/src/main/res/drawable/baseline_check_24.xml
Normal file
5
app/src/main/res/drawable/baseline_check_24.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||||
|
|
||||||
|
</vector>
|
139
app/src/main/res/layout/activity_welcome.xml
Normal file
139
app/src/main/res/layout/activity_welcome.xml
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="20dp"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/updates_title"
|
||||||
|
android:textColor="@color/splash_text"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/check_vendroid_updates"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:checked="true"
|
||||||
|
android:text="@string/autocheck_vendroid" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/autoupdate_vencord"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:checked="true"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text="@string/autocheck_vencord" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Discord branch"
|
||||||
|
android:textColor="@color/splash_text"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/stable"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true"
|
||||||
|
android:text="Stable" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/ptb"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="PTB" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/canary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Canary" />
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Vencord location"
|
||||||
|
android:textColor="@color/splash_text"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/allow_custom_location"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Set custom location (Only check this if you know what you're doing!)" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Custom location URL">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/custom_location"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||||
|
app:contentScrim="?attr/colorSurfaceContainer">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:title="Welcome to Vendroid"
|
||||||
|
app:layout_collapseMode="pin"
|
||||||
|
android:elevation="0dp" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/start_vendroid"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:contentDescription="Start Vendroid"
|
||||||
|
android:text="Start Vendroid"
|
||||||
|
app:icon="@drawable/baseline_check_24" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,4 +1,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Vendroid</string>
|
<string name="app_name">Vendroid</string>
|
||||||
<string name="splashscreen_text">Loading Vendroid…</string>
|
<string name="splashscreen_text">Loading Vendroid…</string>
|
||||||
|
<string name="autocheck_vencord">Automatically update Vencord (Coming soon)</string>
|
||||||
|
<string name="autocheck_vendroid">Automatically check for Vendroid updates</string>
|
||||||
|
<string name="updates_title">Updates</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue