added search to extension list, and bugfix for certain extensions

This commit is contained in:
Finnley Somdahl 2023-10-18 00:28:27 -05:00
parent 7aa8b2db52
commit 3007e7d86e
7 changed files with 278 additions and 41 deletions

View file

@ -21,7 +21,7 @@ android {
minSdk 23
targetSdk 34
versionCode ((System.currentTimeMillis() / 60000).toInteger())
versionName "0.0.1"
versionName "0.0.2"
signingConfig signingConfigs.debug
}

View file

@ -21,6 +21,7 @@ import logcat.LogPriority
import rx.Observable
import ani.dantotsu.aniyomi.util.logcat
import ani.dantotsu.aniyomi.util.withUIContext
import ani.dantotsu.logger
/**
* The manager of anime extensions installed as another apk which extend the available sources. It handles
@ -116,11 +117,13 @@ class AnimeExtensionManager(
}*/
}else{
println("${sc.name} is not AnimeCatalogueSource")
}
}
else -> {
logcat(LogPriority.ERROR) { "Error loading anime extension: $result" }
logcat(LogPriority.ERROR) { "Error loading anime extension: $result." }
}
}
}

View file

@ -14,6 +14,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SearchView
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
@ -29,6 +31,7 @@ import ani.dantotsu.databinding.ActivityExtensionsBinding
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.android.schedulers.AndroidSchedulers
@ -47,16 +50,6 @@ class ExtensionsActivity : AppCompatActivity() {
animeExtensionManager.uninstallExtension(pkgName)
}
private val allExtensionsAdapter = AllExtensionsAdapter(lifecycleScope) { pkgName ->
if (SDK_INT >= VERSION_CODES.O) {
// If we don't have permission to install unknown apps, request it
if (!packageManager.canRequestPackageInstalls()) {
startActivityForResult(
Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
Uri.parse("package:$packageName")
), 1
)
}
}
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -98,7 +91,6 @@ class ExtensionsActivity : AppCompatActivity() {
}
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -119,12 +111,44 @@ class ExtensionsActivity : AppCompatActivity() {
}
}
lifecycleScope.launch {
animeExtensionManager.availableExtensionsFlow.collect { extensions ->
allExtensionsAdapter.updateData(extensions)
combine(
animeExtensionManager.availableExtensionsFlow,
animeExtensionManager.installedExtensionsFlow
) { availableExtensions, installedExtensions ->
// Pair of available and installed extensions
Pair(availableExtensions, installedExtensions)
}.collect { pair ->
val (availableExtensions, installedExtensions) = pair
allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
}
}
val searchView: SearchView = findViewById(R.id.searchView)
val extensionsRecyclerView: RecyclerView = findViewById(R.id.extensionsRecyclerView)
val extensionsHeader: LinearLayout = findViewById(R.id.extensionsHeader)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
if (newText.isNullOrEmpty()) {
allExtensionsAdapter.filter("") // Reset the filter
allextenstionsRecyclerView.visibility = View.VISIBLE
extensionsHeader.visibility = View.VISIBLE
extensionsRecyclerView.visibility = View.VISIBLE
} else {
allExtensionsAdapter.filter(newText)
allextenstionsRecyclerView.visibility = View.VISIBLE
extensionsRecyclerView.visibility = View.GONE
extensionsHeader.visibility = View.GONE
}
return true
}
})
initActivity(this)
@ -180,9 +204,10 @@ class ExtensionsActivity : AppCompatActivity() {
private val onButtonClicked: (AnimeExtension.Available) -> Unit) : RecyclerView.Adapter<AllExtensionsAdapter.ViewHolder>() {
private var extensions: List<AnimeExtension.Available> = emptyList()
fun updateData(newExtensions: List<AnimeExtension.Available>) {
extensions = newExtensions
println("Extensions update: $extensions")
fun updateData(newExtensions: List<AnimeExtension.Available>, installedExtensions: List<AnimeExtension.Installed> = emptyList()) {
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
extensions = newExtensions.filter { it.pkgName !in installedPkgNames }
filteredExtensions = extensions
notifyDataSetChanged()
}
@ -193,7 +218,7 @@ class ExtensionsActivity : AppCompatActivity() {
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position]
val extension = filteredExtensions[position]
holder.extensionNameTextView.text = extension.name
coroutineScope.launch {
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl)
@ -205,7 +230,18 @@ class ExtensionsActivity : AppCompatActivity() {
}
}
override fun getItemCount(): Int = extensions.size
override fun getItemCount(): Int = filteredExtensions.size
private var filteredExtensions: List<AnimeExtension.Available> = emptyList()
fun filter(query: String) {
filteredExtensions = if (query.isEmpty()) {
extensions
} else {
extensions.filter { it.name.contains(query, ignoreCase = true) }
}
notifyDataSetChanged()
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)

View file

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi
import tachiyomi.core.util.system.ImageUtil
/**
* Used by extensions.
*/
@Suppress("UNUSED")
object AppInfo {
/**
* Version code of the host application. May be useful for sharing as User-Agent information.
* Note that this value differs between forks so logic should not rely on it.
*
* @since extension-lib 1.3
*/
fun getVersionCode(): Int = ani.dantotsu.BuildConfig.VERSION_CODE
/**
* Version name of the host application. May be useful for sharing as User-Agent information.
* Note that this value differs between forks so logic should not rely on it.
*
* @since extension-lib 1.3
*/
fun getVersionName(): String = ani.dantotsu.BuildConfig.VERSION_NAME
/**
* A list of supported image MIME types by the reader.
* e.g. ["image/jpeg", "image/png", ...]
*
* @since extension-lib 1.5
*/
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.values().map { it.mime }
}

View file

@ -0,0 +1,133 @@
package tachiyomi.core.util.system
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.webkit.MimeTypeMap
import androidx.annotation.ColorInt
import androidx.core.graphics.alpha
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.blue
import androidx.core.graphics.createBitmap
import androidx.core.graphics.get
import androidx.core.graphics.green
import androidx.core.graphics.red
import com.hippo.unifile.UniFile
import logcat.LogPriority
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URLConnection
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
object ImageUtil {
enum class ImageType(val mime: String, val extension: String) {
AVIF("image/avif", "avif"),
GIF("image/gif", "gif"),
HEIF("image/heif", "heif"),
JPEG("image/jpeg", "jpg"),
JXL("image/jxl", "jxl"),
PNG("image/png", "png"),
WEBP("image/webp", "webp"),
}
/**
* Extract the 'side' part from imageStream and return it as InputStream.
*/
fun splitInHalf(imageStream: InputStream, side: Side): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val height = imageBitmap.height
val width = imageBitmap.width
val singlePage = Rect(0, 0, width / 2, height)
val half = createBitmap(width / 2, height)
val part = when (side) {
Side.RIGHT -> Rect(width - width / 2, 0, width, height)
Side.LEFT -> Rect(0, 0, width / 2, height)
}
half.applyCanvas {
drawBitmap(imageBitmap, part, singlePage, null)
}
val output = ByteArrayOutputStream()
half.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
}
fun rotateImage(imageStream: InputStream, degrees: Float): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val rotated = rotateBitMap(imageBitmap, degrees)
val output = ByteArrayOutputStream()
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
}
private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap {
val matrix = Matrix().apply { postRotate(degrees) }
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
/**
* Split the image into left and right parts, then merge them into a new image.
*/
fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val height = imageBitmap.height
val width = imageBitmap.width
val result = createBitmap(width / 2, height * 2)
result.applyCanvas {
// right -> upper
val rightPart = when (upperSide) {
Side.RIGHT -> Rect(width - width / 2, 0, width, height)
Side.LEFT -> Rect(0, 0, width / 2, height)
}
val upperPart = Rect(0, 0, width / 2, height)
drawBitmap(imageBitmap, rightPart, upperPart, null)
// left -> bottom
val leftPart = when (upperSide) {
Side.LEFT -> Rect(width - width / 2, 0, width, height)
Side.RIGHT -> Rect(0, 0, width / 2, height)
}
val bottomPart = Rect(0, height, width / 2, height * 2)
drawBitmap(imageBitmap, leftPart, bottomPart, null)
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
}
enum class Side {
RIGHT,
LEFT,
}
}
val getDisplayMaxHeightInPx: Int
get() = Resources.getSystem().displayMetrics.let { max(it.heightPixels, it.widthPixels) }

View file

@ -17,14 +17,19 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
app:cardBackgroundColor="@color/nav_bg_inv"
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
app:cardElevation="0dp"
>
<ImageButton
android:id="@+id/settingsBack"
@ -37,7 +42,17 @@
tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
</androidx.cardview.widget.CardView>
<SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layoutDirection="rtl"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<LinearLayout
android:id="@+id/extensionsHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@ -77,20 +92,20 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/extensionsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="32dp"
android:layout_weight="1"
android:textSize="28sp"
android:text="All Extensions"/>
android:text="All Extensions"
android:textSize="28sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/allExtensionsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent" />
</LinearLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!-- Need to allow cleartext traffic for some sources -->
<base-config
cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration">
<trust-anchors>
<!-- Trust preinstalled CAs -->
<certificates src="system" />
<!-- Additionally trust user added CAs -->
<certificates
src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>