Initial commit
This commit is contained in:
commit
21bfbfb139
520 changed files with 47819 additions and 0 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue