Skip to content

Commit 27d2ff7

Browse files
keyword
1 parent b59cd17 commit 27d2ff7

8 files changed

Lines changed: 567 additions & 38 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@
273273
android:theme="@style/Theme.Transparent">
274274
</activity>
275275

276+
<activity android:name=".activity.KeywordSearchActivity"
277+
android:theme="@style/AppTheme">
278+
</activity>
279+
276280
<provider
277281
android:name="androidx.core.content.FileProvider"
278282
android:authorities="${applicationId}.provider"

app/src/main/java/com/wirelessalien/android/moviedb/activity/DetailActivity.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4632,6 +4632,7 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated
46324632
binding.keywordsLayout.removeAllViews()
46334633
for (i in 0 until keywordsArray.length()) {
46344634
val keyword = keywordsArray.getJSONObject(i)
4635+
val keywordId = keyword.getInt("id")
46354636
val keywordName = keyword.getString("name")
46364637
val cardView = MaterialCardView(context)
46374638
cardView.radius = 5f
@@ -4643,6 +4644,12 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated
46434644
keywordTextView.setPadding(8, 4, 8, 4)
46444645
keywordTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f)
46454646
cardView.addView(keywordTextView)
4647+
cardView.setOnClickListener {
4648+
val intent = Intent(context, KeywordSearchActivity::class.java)
4649+
intent.putExtra("keywordId", keywordId)
4650+
intent.putExtra("keywordName", keywordName)
4651+
startActivity(intent)
4652+
}
46464653
val params = FlexboxLayout.LayoutParams(
46474654
FlexboxLayout.LayoutParams.WRAP_CONTENT,
46484655
FlexboxLayout.LayoutParams.WRAP_CONTENT
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* This file is part of "ShowCase" formerly Movie DB. <https://github.com/WirelessAlien/MovieDB>
3+
* forked from <https://notabug.org/nvb/MovieDB>
4+
*
5+
* Copyright (C) 2024 WirelessAlien <https://github.com/WirelessAlien>
6+
*
7+
* ShowCase is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* ShowCase is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with "ShowCase". If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
package com.wirelessalien.android.moviedb.activity
21+
22+
import android.content.Intent
23+
import android.content.SharedPreferences
24+
import android.os.Bundle
25+
import android.view.MenuItem
26+
import android.view.View
27+
import android.widget.Toast
28+
import androidx.activity.viewModels
29+
import androidx.appcompat.app.AppCompatActivity
30+
import androidx.lifecycle.lifecycleScope
31+
import androidx.paging.LoadState
32+
import androidx.preference.PreferenceManager
33+
import androidx.recyclerview.widget.GridLayoutManager
34+
import androidx.recyclerview.widget.LinearLayoutManager
35+
import com.wirelessalien.android.moviedb.R
36+
import com.wirelessalien.android.moviedb.adapter.ExternalSearchAdapter
37+
import com.wirelessalien.android.moviedb.adapter.ShowPagingAdapter
38+
import com.wirelessalien.android.moviedb.databinding.ActivityKeywordSearchBinding
39+
import com.wirelessalien.android.moviedb.fragment.BaseFragment
40+
import com.wirelessalien.android.moviedb.helper.ThemeHelper
41+
import com.wirelessalien.android.moviedb.viewmodel.KeywordSearchViewModel
42+
import kotlinx.coroutines.flow.collectLatest
43+
import kotlinx.coroutines.launch
44+
45+
class KeywordSearchActivity : AppCompatActivity() {
46+
47+
private lateinit var binding: ActivityKeywordSearchBinding
48+
private lateinit var searchAdapter: ShowPagingAdapter
49+
private var keywordId: Int = -1
50+
private var keywordName: String? = null
51+
private lateinit var preferences: SharedPreferences
52+
private val viewModel: KeywordSearchViewModel by viewModels()
53+
54+
override fun onCreate(savedInstanceState: Bundle?) {
55+
ThemeHelper.applyAmoledTheme(this)
56+
57+
super.onCreate(savedInstanceState)
58+
binding = ActivityKeywordSearchBinding.inflate(layoutInflater)
59+
setContentView(binding.root)
60+
preferences = PreferenceManager.getDefaultSharedPreferences(this)
61+
62+
keywordId = intent.getIntExtra("keywordId", -1)
63+
keywordName = intent.getStringExtra("keywordName")
64+
65+
setSupportActionBar(binding.toolbar)
66+
supportActionBar?.setDisplayHomeAsUpEnabled(true)
67+
supportActionBar?.title = keywordName
68+
69+
setupRecyclerView()
70+
setupChips()
71+
72+
if (keywordId != -1) {
73+
search("movie")
74+
}
75+
}
76+
77+
private fun setupChips() {
78+
binding.chipMovie.setOnCheckedChangeListener { _, isChecked ->
79+
if (isChecked) {
80+
search("movie")
81+
}
82+
}
83+
binding.chipShow.setOnCheckedChangeListener { _, isChecked ->
84+
if (isChecked) {
85+
search("tv")
86+
}
87+
}
88+
}
89+
90+
private fun setupRecyclerView() {
91+
searchAdapter = ShowPagingAdapter(HashMap(), preferences.getBoolean(BaseFragment.SHOWS_LIST_PREFERENCE, false), false)
92+
binding.recyclerView.apply {
93+
layoutManager = if (preferences.getBoolean(BaseFragment.SHOWS_LIST_PREFERENCE, false)) GridLayoutManager(this@KeywordSearchActivity, preferences.getInt(
94+
BaseFragment.GRID_SIZE_PREFERENCE, 3).coerceAtLeast(1)) else LinearLayoutManager(this@KeywordSearchActivity)
95+
adapter = searchAdapter
96+
}
97+
}
98+
99+
private fun search(mediaType: String) {
100+
lifecycleScope.launch {
101+
viewModel.search(keywordId, mediaType, this@KeywordSearchActivity).collectLatest {
102+
searchAdapter.submitData(it)
103+
}
104+
}
105+
106+
searchAdapter.addLoadStateListener { loadState ->
107+
when (loadState.source.refresh) {
108+
is LoadState.Loading -> {
109+
binding.recyclerView.visibility = View.GONE
110+
binding.shimmerFrameLayout.visibility = View.VISIBLE
111+
binding.shimmerFrameLayout.startShimmer()
112+
}
113+
114+
is LoadState.NotLoading -> {
115+
binding.recyclerView.visibility = View.VISIBLE
116+
binding.shimmerFrameLayout.stopShimmer()
117+
binding.shimmerFrameLayout.visibility = View.GONE
118+
}
119+
120+
is LoadState.Error -> {
121+
binding.recyclerView.visibility = View.VISIBLE
122+
binding.shimmerFrameLayout.stopShimmer()
123+
binding.shimmerFrameLayout.visibility = View.GONE
124+
val errorMessage = (loadState.source.refresh as LoadState.Error).error.message
125+
Toast.makeText(this, getString(R.string.error_loading_data) + ": " + errorMessage, Toast.LENGTH_SHORT).show()
126+
}
127+
}
128+
}
129+
}
130+
131+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
132+
return when (item.itemId) {
133+
android.R.id.home -> {
134+
onBackPressedDispatcher.onBackPressed()
135+
true
136+
}
137+
else -> super.onOptionsItemSelected(item)
138+
}
139+
}
140+
}

app/src/main/java/com/wirelessalien/android/moviedb/activity/MainActivity.kt

Lines changed: 127 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,53 @@ class MainActivity : BaseActivity() {
557557
private fun setupSearchView() {
558558
val liveSearch = preferences.getBoolean(LIVE_SEARCH_PREFERENCE, false)
559559

560+
val doSearch = { query: String ->
561+
val isShowMovieChecked = binding.chipShowMovie.isChecked
562+
val isMovieChecked = binding.chipMovie.isChecked
563+
val isShowChecked = binding.chipShow.isChecked
564+
565+
val currentFragment = getCurrentFragment()
566+
if (currentFragment is HomeFragment || currentFragment is AccountDataFragment || currentFragment is AccountDataFragmentTkt) {
567+
if (isShowMovieChecked) {
568+
multiSearch(query)
569+
} else if (isMovieChecked) {
570+
showSearch("movie", query)
571+
} else if (isShowChecked) {
572+
showSearch("tv", query)
573+
}
574+
} else if (currentFragment is ShowFragment) {
575+
val listType = currentFragment.arguments?.getString(ShowFragment.ARG_LIST_TYPE)
576+
if (listType == "movie") {
577+
showSearch(listType, query)
578+
} else if (listType == "tv") {
579+
showSearch(listType, query)
580+
}
581+
} else if (currentFragment is ListFragment) {
582+
databaseSearch(query)
583+
}
584+
585+
fetchKeywords(query)
586+
}
587+
588+
binding.chipShowMovie.setOnCheckedChangeListener { _, isChecked ->
589+
if (isChecked) {
590+
val query = binding.searchView.editText.text.toString()
591+
if (query.isNotEmpty()) doSearch(query)
592+
}
593+
}
594+
binding.chipMovie.setOnCheckedChangeListener { _, isChecked ->
595+
if (isChecked) {
596+
val query = binding.searchView.editText.text.toString()
597+
if (query.isNotEmpty()) doSearch(query)
598+
}
599+
}
600+
binding.chipShow.setOnCheckedChangeListener { _, isChecked ->
601+
if (isChecked) {
602+
val query = binding.searchView.editText.text.toString()
603+
if (query.isNotEmpty()) doSearch(query)
604+
}
605+
}
606+
560607
if (liveSearch) {
561608
binding.searchView.editText.addTextChangedListener(object : TextWatcher {
562609
private val handler = Handler(Looper.getMainLooper())
@@ -567,23 +614,7 @@ class MainActivity : BaseActivity() {
567614
handler.removeCallbacks(workRunnable!!)
568615
}
569616
workRunnable = Runnable {
570-
val currentFragment = getCurrentFragment()
571-
if (currentFragment is HomeFragment) {
572-
multiSearch(s.toString())
573-
} else if (currentFragment is ShowFragment) {
574-
val listType = currentFragment.arguments?.getString(ShowFragment.ARG_LIST_TYPE)
575-
if (listType == "movie") {
576-
showSearch(listType, s.toString())
577-
} else if (listType == "tv") {
578-
showSearch(listType, s.toString())
579-
}
580-
} else if (currentFragment is ListFragment) {
581-
databaseSearch(s.toString())
582-
} else if (currentFragment is AccountDataFragment) {
583-
multiSearch(s.toString())
584-
} else if (currentFragment is AccountDataFragmentTkt) {
585-
multiSearch(s.toString())
586-
}
617+
doSearch(s.toString())
587618
}
588619
handler.postDelayed(workRunnable!!, 500)
589620
}
@@ -596,30 +627,92 @@ class MainActivity : BaseActivity() {
596627
actionId == EditorInfo.IME_ACTION_SEARCH ||
597628
event?.action == KeyEvent.ACTION_DOWN && event.keyCode == KeyEvent.KEYCODE_ENTER) {
598629
val query = v.text.toString()
599-
val currentFragment = getCurrentFragment()
600-
if (currentFragment is HomeFragment) {
601-
multiSearch(query)
602-
} else if (currentFragment is ShowFragment) {
603-
val listType = currentFragment.arguments?.getString(ShowFragment.ARG_LIST_TYPE)
604-
if (listType == "movie") {
605-
showSearch(listType, query)
606-
} else if (listType == "tv") {
607-
showSearch(listType, query)
608-
}
609-
} else if (currentFragment is ListFragment) {
610-
databaseSearch(query)
611-
} else if (currentFragment is AccountDataFragment) {
612-
multiSearch(query)
613-
} else if (currentFragment is AccountDataFragmentTkt) {
614-
multiSearch(query)
615-
}
630+
doSearch(query)
616631
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
617632
imm.hideSoftInputFromWindow(v.windowToken, 0)
618633
true
619634
} else {
620635
false
621636
}
622637
}
638+
639+
binding.searchView.editText.addTextChangedListener(object : TextWatcher {
640+
private val handler = Handler(Looper.getMainLooper())
641+
private var workRunnable: Runnable? = null
642+
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
643+
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
644+
if (workRunnable != null) {
645+
handler.removeCallbacks(workRunnable!!)
646+
}
647+
workRunnable = Runnable {
648+
fetchKeywords(s.toString())
649+
}
650+
handler.postDelayed(workRunnable!!, 500)
651+
}
652+
653+
override fun afterTextChanged(s: Editable) {}
654+
})
655+
}
656+
}
657+
658+
private fun fetchKeywords(query: String) {
659+
if (query.isEmpty()) {
660+
binding.searchChipGroup.removeViews(3, binding.searchChipGroup.childCount - 3)
661+
return
662+
}
663+
664+
lifecycleScope.launch(Dispatchers.IO) {
665+
try {
666+
val url = java.net.URL("https://api.themoviedb.org/3/search/keyword?query=${query}&page=1")
667+
val request = Request.Builder()
668+
.url(url)
669+
.get()
670+
.addHeader("Authorization", "Bearer $apiReadAccessToken")
671+
.build()
672+
673+
val client = OkHttpClient()
674+
val response = client.newCall(request).execute()
675+
val responseBody = response.body?.string() ?: ""
676+
677+
if (response.isSuccessful && responseBody.isNotEmpty()) {
678+
val json = JSONObject(responseBody)
679+
val results = json.optJSONArray("results")
680+
681+
withContext(Dispatchers.Main) {
682+
binding.searchChipGroup.removeViews(3, binding.searchChipGroup.childCount - 3)
683+
684+
if (results != null) {
685+
for (i in 0 until Math.min(results.length(), 10)) {
686+
val item = results.getJSONObject(i)
687+
val keywordId = item.getInt("id")
688+
val keywordName = item.getString("name")
689+
690+
val chip = Chip(this@MainActivity)
691+
chip.text = keywordName
692+
chip.isCheckable = true
693+
chip.setChipDrawable(com.google.android.material.chip.ChipDrawable.createFromAttributes(
694+
this@MainActivity,
695+
null,
696+
0,
697+
com.google.android.material.R.style.Widget_Material3_Chip_Filter
698+
))
699+
chip.setOnCheckedChangeListener { _, isChecked ->
700+
if (isChecked) {
701+
chip.isChecked = false
702+
val intent = Intent(this@MainActivity, KeywordSearchActivity::class.java)
703+
intent.putExtra("keywordId", keywordId)
704+
intent.putExtra("keywordName", keywordName)
705+
startActivity(intent)
706+
}
707+
}
708+
binding.searchChipGroup.addView(chip)
709+
}
710+
}
711+
}
712+
}
713+
} catch (e: Exception) {
714+
e.printStackTrace()
715+
}
623716
}
624717
}
625718

0 commit comments

Comments
 (0)