This commit is contained in:
lunaticbum 2025-08-11 17:32:22 +09:00
parent d1f080ded1
commit 69d93778a4
7 changed files with 280 additions and 29 deletions

View File

@ -26,6 +26,7 @@ import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -42,8 +43,12 @@ import android.view.KeyEvent.KEYCODE_BUTTON_Y
import android.view.KeyEvent.KEYCODE_DPAD_DOWN import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_UP import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.view.WindowManager import android.view.WindowManager
import android.widget.Button
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -489,7 +494,7 @@ internal class LauncherActivity : CommonActivity() {
// } // }
super.onNewIntent(intent) super.onNewIntent(intent)
} }
var isKeyboardVisible = false
@SuppressLint("NewApi", "MissingPermission") @SuppressLint("NewApi", "MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -509,6 +514,7 @@ internal class LauncherActivity : CommonActivity() {
HeadsetActionButtonReceiver.register(this) HeadsetActionButtonReceiver.register(this)
binding.tabs.setOnCheckedChangeListener { g, id -> binding.tabs.setOnCheckedChangeListener { g, id ->
showContents(id) showContents(id)
} }

View File

@ -49,6 +49,7 @@ import bums.lunatic.launcher.R
import bums.lunatic.launcher.common.letTrue import bums.lunatic.launcher.common.letTrue
import bums.lunatic.launcher.databinding.LauncherHomeBinding import bums.lunatic.launcher.databinding.LauncherHomeBinding
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
import bums.lunatic.launcher.home.SearchBottomSheet.OnSearchListener
import bums.lunatic.launcher.home.adapters.RssItemAdapter import bums.lunatic.launcher.home.adapters.RssItemAdapter
import bums.lunatic.launcher.home.adapters.SwipeToDeleteCallback import bums.lunatic.launcher.home.adapters.SwipeToDeleteCallback
import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssData
@ -272,34 +273,47 @@ internal class RssHome : Fragment() {
} }
} }
fun searchKeyword() { fun searchKeyword() {
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) // val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
builder.setTitle("Keyword") // builder.setTitle("Keyword")
val viewInflated: View = LayoutInflater.from(requireContext()) // val viewInflated: View = LayoutInflater.from(requireContext())
.inflate(R.layout.text_inpu_password, binding.root as ViewGroup?, false) // .inflate(R.layout.text_inpu_password, binding.root as ViewGroup?, false)
val input = viewInflated.findViewById<View>(R.id.input) as EditText // val input = viewInflated.findViewById<View>(R.id.input) as EditText
val privateMode = viewInflated.findViewById<CheckBox>(R.id.private_mode) as CheckBox // val privateMode = viewInflated.findViewById<CheckBox>(R.id.private_mode) as CheckBox
val addVote = viewInflated.findViewById<CheckBox>(R.id.add_vote) as CheckBox // val addVote = viewInflated.findViewById<CheckBox>(R.id.add_vote) as CheckBox
val addRead = viewInflated.findViewById<CheckBox>(R.id.add_read) as CheckBox // val addRead = viewInflated.findViewById<CheckBox>(R.id.add_read) as CheckBox
privateMode.setOnCheckedChangeListener { v,c-> // privateMode.setOnCheckedChangeListener { v,c->
binding.geckoWeb.privateMode = c // binding.geckoWeb.privateMode = c
} // }
privateMode.visibility = View.GONE // privateMode.visibility = View.GONE
binding.geckoWeb.privateMode = true // binding.geckoWeb.privateMode = true
builder.setView(viewInflated) // builder.setView(viewInflated)
builder.setPositiveButton( // builder.setPositiveButton(
android.R.string.ok, // android.R.string.ok,
DialogInterface.OnClickListener { dialog, which -> // DialogInterface.OnClickListener { dialog, which ->
dialog.dismiss() // dialog.dismiss()
var command = input.editableText?.toString() // var command = input.editableText?.toString()
if (command?.length ?: 0 > 0) { // if (command?.length ?: 0 > 0) {
queryInfos(keywords = command!!.split(" ")!!, addVote.isChecked, addRead.isChecked) // queryInfos(keywords = command!!.split(" ")!!, addVote.isChecked, addRead.isChecked)
} // }
}) // })
builder.setNegativeButton( // builder.setNegativeButton(
android.R.string.cancel, // android.R.string.cancel,
DialogInterface.OnClickListener { dialog, which -> dialog.cancel() }) // DialogInterface.OnClickListener { dialog, which -> dialog.cancel() })
//
// builder.show()
builder.show() val bottomSheet = SearchBottomSheet()
bottomSheet.listener = object : OnSearchListener{
override fun onSearch(
keyword: String,
category: List<String>,
addReaded: Boolean,
addVoted: Boolean
) {
queryInfos(keywords = keyword.split(" "), category , addReaded, addVoted)
}
}
bottomSheet.show(childFragmentManager, "SearchBottomSheet")
} }
@ -730,6 +744,7 @@ internal class RssHome : Fragment() {
fun queryInfos( fun queryInfos(
keywords: List<String>, keywords: List<String>,
category: List<String>,
includeVote : Boolean = false, includeVote : Boolean = false,
includeRead : Boolean = false includeRead : Boolean = false
) { ) {
@ -737,6 +752,9 @@ internal class RssHome : Fragment() {
var rQ = getRealm().query<RssData>().sort("read", Sort.ASCENDING) var rQ = getRealm().query<RssData>().sort("read", Sort.ASCENDING)
if (!includeRead) { rQ = rQ.query("read == $0", 0)} if (!includeRead) { rQ = rQ.query("read == $0", 0)}
if (!includeVote) { rQ = rQ.query("vote != $0", true)} if (!includeVote) { rQ = rQ.query("vote != $0", true)}
keywords.forEach {
rQ = rQ.query("category != $0", it)
}
// 사용 예시 // 사용 예시
val (queryStr, queryArgs) = buildMultiFieldOrQuery( val (queryStr, queryArgs) = buildMultiFieldOrQuery(
listOf("title", "description"), listOf("title", "description"),

View File

@ -0,0 +1,143 @@
package bums.lunatic.launcher.home
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioGroup
import android.widget.Toast
import bums.lunatic.launcher.R
import bums.lunatic.launcher.model.RssDataType
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class SearchBottomSheet : BottomSheetDialogFragment() {
private var selectedCategory = HashSet<String>()
private val debounceHandler = Handler(Looper.getMainLooper())
private var searchRunnable: Runnable? = null
private val debounceDelay = 300L // 0.3초 지연
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet_search, container, false)
}
interface OnSearchListener {
fun onSearch(keyword: String, category: List<String>, addReaded : Boolean, addVoted: Boolean)
}
var listener: OnSearchListener? = null
private val categoryStates = mutableMapOf<String, Boolean>()
lateinit var addVote : CheckBox
lateinit var addRead : CheckBox
lateinit var inputKeyword : EditText
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
inputKeyword = view.findViewById<EditText>(R.id.inputKeyword)
val categoryContainer = view.findViewById<LinearLayout>(R.id.categoryContainer)
addVote = view.findViewById<CheckBox>(R.id.add_vote) as CheckBox
addRead = view.findViewById<CheckBox>(R.id.add_read) as CheckBox
addRead.setOnCheckedChangeListener {v,b->triggerSearchWithDebounce(inputKeyword.text.toString())}
addRead.setOnCheckedChangeListener {v,b->triggerSearchWithDebounce(inputKeyword.text.toString())}
// 카테고리 목록
val categories = RssDataType.getAll()
categories.forEach { categoryStates[it.name] = true }
// 버튼 동적 생성
categoryContainer.removeAllViews()
for (category in categories) {
val btn = Button(requireContext()).apply {
text = category.name
isAllCaps = false
setBackgroundResource(android.R.drawable.btn_default)
// 초기 색상(활성 상태 색 표시)
updateButtonStyle(this, true)
setOnClickListener {
// 상태 반전
val current = categoryStates[category.name] ?: true
categoryStates[category.name] = !current
// 스타일 업데이트
updateButtonStyle(this, !current)
triggerSearchWithDebounce(inputKeyword.text.toString())
}
}
categoryContainer.addView(btn)
}
inputKeyword.requestFocus()
// 키보드 강제 오픈
inputKeyword.postDelayed( {
val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(inputKeyword, InputMethodManager.SHOW_IMPLICIT)
},150L)
// EditText 입력 실시간 감지 + debounce
inputKeyword.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
triggerSearchWithDebounce(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// 키보드 "검색" 액션 처리
inputKeyword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
val keyword = inputKeyword.text.toString()
if (keyword.isNotEmpty()) {
triggerSearchWithDebounce(keyword)
dismiss() // 필요 시 닫기
}
true
} else {
false
}
}
}
private fun updateButtonStyle(button: Button, isActive: Boolean) {
if (isActive) {
button.setBackgroundColor(Color.parseColor("#FF9800")) // 활성화 색상
button.setTextColor(Color.WHITE)
} else {
button.setBackgroundColor(Color.LTGRAY) // 비활성화 색상
button.setTextColor(Color.DKGRAY)
}
}
private fun triggerSearchWithDebounce(keyword: String) {
searchRunnable?.let { debounceHandler.removeCallbacks(it) }
searchRunnable = Runnable {
val disabledCategories = categoryStates.filter { it.value == false }.keys.toList()
listener?.onSearch(keyword, disabledCategories.toList(), addRead.isChecked,addVote.isChecked)
}
debounceHandler.postDelayed(searchRunnable!!, debounceDelay)
}
override fun onStart() {
super.onStart()
// 전체 창 높이를 키보드에 맞게 조정
dialog?.window?.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE or
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
}
}

View File

@ -56,7 +56,7 @@ enum class RssDataType {
companion object { companion object {
fun getExcAdt() = arrayListOf(NEWSFEED, REDDIT, DOTAX, FMKORAE, DCINSIDE, RULIWEB, CLIEN, THEQOO, ARCA).map { it.name } fun getExcAdt() = arrayListOf(NEWSFEED, REDDIT, DOTAX, FMKORAE, DCINSIDE, RULIWEB, CLIEN, THEQOO, ARCA).map { it.name }
fun getAdts() = arrayListOf( REDDIT_NSFW).map { it.name } fun getAdts() = arrayListOf( REDDIT_NSFW).map { it.name }
//, MOST,GURU fun getAll() = arrayListOf(TORRENT, YOUTUBE, NEWSFEED, REDDIT, DOTAX, FMKORAE, DCINSIDE, RULIWEB, CLIEN, THEQOO, ARCA,PRIVATE,REDDIT_NSFW)
} }
} }

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 검색어 입력 -->
<EditText
android:id="@+id/inputKeyword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="검색어 입력"
android:imeOptions="actionSearch"
android:inputType="text"
android:singleLine="true" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:layout_weight="1"
android:padding="0dp"
android:text="add Vote"
android:textColor="@color/black"
android:id="@+id/add_vote"
android:checked="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<CheckBox
android:layout_weight="1"
android:padding="0dp"
android:text="Add Read"
android:textColor="@color/black"
android:id="@+id/add_read"
android:checked="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 카테고리 툴바 -->
<HorizontalScrollView
android:id="@+id/accessoryToolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="#F0F0F0"
android:scrollbars="none">
<LinearLayout
android:id="@+id/categoryContainer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical" />
</HorizontalScrollView>
</LinearLayout>

View File

@ -79,4 +79,7 @@
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</RadioGroup> </RadioGroup>
</HorizontalScrollView> </HorizontalScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -59,4 +59,26 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<HorizontalScrollView
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/accessoryToolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:visibility="gone"
android:background="#F0F0F0"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/categoryContainer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"/>
</HorizontalScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>