Initial commit

This commit is contained in:
Finnley Somdahl 2023-10-17 18:42:43 -05:00
commit 21bfbfb139
520 changed files with 47819 additions and 0 deletions

View file

@ -0,0 +1,98 @@
package ani.dantotsu.others.imagesearch
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.App.Companion.context
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.ActivityImageSearchBinding
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.toast
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ImageSearchActivity : AppCompatActivity() {
private lateinit var binding: ActivityImageSearchBinding
private val viewModel: ImageSearchViewModel by viewModels()
private val imageSelectionLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let { imageUri ->
val contentResolver = applicationContext.contentResolver
lifecycleScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
binding.progressBar.visibility = View.VISIBLE
}
val inputStream = contentResolver.openInputStream(imageUri)
if(inputStream != null) viewModel.analyzeImage(inputStream)
else toast(getString(R.string.error_loading_image))
withContext(Dispatchers.Main) {
binding.progressBar.visibility = View.GONE
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityImageSearchBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.uploadImage.setOnClickListener {
viewModel.clearResults()
imageSelectionLauncher.launch("image/*")
}
binding.imageSearchTitle.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
viewModel.searchResultLiveData.observe(this) { result ->
result?.let { displayResult(it) }
}
}
private fun displayResult(result: ImageSearchViewModel.SearchResult) {
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
val searchResults: List<ImageSearchViewModel.ImageResult> = result.result.orEmpty()
val adapter = ImageSearchResultAdapter(searchResults)
adapter.setOnItemClickListener(object : ImageSearchResultAdapter.OnItemClickListener {
override fun onItemClick(searchResult: ImageSearchViewModel.ImageResult) {
lifecycleScope.launch(Dispatchers.IO) {
val id = searchResult.anilist?.id?.toInt()
if (id==null){
toast(getString(R.string.no_anilist_id_found))
return@launch
}
val media = Anilist.query.getMedia(id, false)
withContext(Dispatchers.Main) {
media?.let {
startActivity(
Intent(this@ImageSearchActivity, MediaDetailsActivity::class.java)
.putExtra("media", it)
)
}
}
}
}
})
recyclerView.post {
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(context)
}
}
}

View file

@ -0,0 +1,67 @@
package ani.dantotsu.others.imagesearch
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemSearchByImageBinding
import ani.dantotsu.loadImage
class ImageSearchResultAdapter(private val searchResults: List<ImageSearchViewModel.ImageResult>) :
RecyclerView.Adapter<ImageSearchResultAdapter.SearchResultViewHolder>() {
interface OnItemClickListener {
fun onItemClick(searchResult: ImageSearchViewModel.ImageResult)
}
private var itemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
itemClickListener = listener
}
inner class SearchResultViewHolder(val binding : ItemSearchByImageBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchResultViewHolder {
val binding = ItemSearchByImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SearchResultViewHolder(binding)
}
override fun onBindViewHolder(holder: SearchResultViewHolder, position: Int) {
val searchResult = searchResults[position]
val binding = holder.binding
binding.root.setOnClickListener {
itemClickListener?.onItemClick(searchResult)
}
binding.root.context.apply {
binding.itemCompactTitle.text = searchResult.anilist?.title?.romaji
binding.itemTotal.text = getString(
R.string.similarity_text, String.format("%.1f", searchResult.similarity?.times(100))
)
binding.episodeNumber.text = getString(R.string.episode_num, searchResult.episode.toString())
binding.timeStamp.text = getString(
R.string.time_range,
toTimestamp(searchResult.from),
toTimestamp(searchResult.to)
)
binding.itemImage.loadImage(searchResult.image)
}
}
override fun getItemCount(): Int {
return searchResults.size
}
private fun toTimestamp(seconds: Double?): String {
val minutes = (seconds?.div(60))?.toInt()
val remainingSeconds = (seconds?.mod(60.0))?.toInt()
val minutesString = minutes.toString().padStart(2, '0')
val secondsString = remainingSeconds.toString().padStart(2, '0')
return "$minutesString:$secondsString"
}
}

View file

@ -0,0 +1,78 @@
package ani.dantotsu.others.imagesearch
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import ani.dantotsu.client
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.InputStream
class ImageSearchViewModel : ViewModel() {
val searchResultLiveData: MutableLiveData<SearchResult> = MutableLiveData()
private val url = "https://api.trace.moe/search?cutBorders&anilistInfo"
suspend fun analyzeImage(inputStream: InputStream) {
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"image",
"image.jpg",
inputStream.readBytes().toRequestBody("image/jpeg".toMediaType())
)
.build()
val res = client.post(url, requestBody = requestBody).parsed<SearchResult>()
searchResultLiveData.postValue(res)
}
fun clearResults(){
searchResultLiveData.postValue(SearchResult())
}
@Serializable
data class SearchResult(
val frameCount: Long? = null,
val error: String? = null,
val result: List<ImageResult>? = null
)
@Serializable
data class ImageResult(
val anilist: AnilistData? = null,
val filename: String? = null,
@SerialName("episode") val rawEpisode: JsonElement? = null,
val from: Double? = null,
val to: Double? = null,
val similarity: Double? = null,
val video: String? = null,
val image: String? = null
) {
val episode: String?
get() = rawEpisode?.toString()
override fun toString(): String {
return "$image & $video"
}
}
@Serializable
data class AnilistData(
val id: Long? = null,
val idMal: Long? = null,
val title: Title? = null,
val synonyms: List<String>? = null,
val isAdult: Boolean? = null
)
@Serializable
data class Title(
val native: String? = null,
val romaji: String? = null,
val english: String? = null
)
}