From 7df7a783f601dfc66916dcf919ed141e25e3c5ae Mon Sep 17 00:00:00 2001 From: tarsin Date: Tue, 27 Jun 2023 18:33:08 +0800 Subject: [PATCH] Initial migrate SearchLayout to Compose TODO: advanced options --- .../main/java/com/hippo/ehviewer/Settings.kt | 3 +- .../ehviewer/ui/legacy/AdvanceSearchTable.kt | 127 +---- .../ehviewer/ui/legacy/ImageSearchLayout.kt | 106 ---- .../ehviewer/ui/legacy/RadioGridGroup.kt | 243 --------- .../hippo/ehviewer/ui/legacy/SearchLayout.kt | 466 +++++------------- .../ehviewer/ui/legacy/SimpleGridLayout.kt | 164 ------ .../com/hippo/ehviewer/ui/main/ImageSearch.kt | 61 +++ .../hippo/ehviewer/ui/main/NormalSearch.kt | 100 ++++ .../ehviewer/ui/scene/GalleryListScene.kt | 23 +- app/src/main/res/layout/filter_chip.xml | 5 - app/src/main/res/layout/search_advance.xml | 19 - app/src/main/res/layout/search_category.xml | 46 -- app/src/main/res/layout/search_image.xml | 20 - app/src/main/res/layout/search_normal.xml | 103 ---- .../layout/widget_advance_search_table.xml | 178 ------- 15 files changed, 298 insertions(+), 1366 deletions(-) delete mode 100644 app/src/main/java/com/hippo/ehviewer/ui/legacy/ImageSearchLayout.kt delete mode 100644 app/src/main/java/com/hippo/ehviewer/ui/legacy/RadioGridGroup.kt delete mode 100644 app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleGridLayout.kt create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/main/ImageSearch.kt create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/main/NormalSearch.kt delete mode 100644 app/src/main/res/layout/filter_chip.xml delete mode 100644 app/src/main/res/layout/search_advance.xml delete mode 100644 app/src/main/res/layout/search_category.xml delete mode 100644 app/src/main/res/layout/search_image.xml delete mode 100644 app/src/main/res/layout/search_normal.xml delete mode 100644 app/src/main/res/layout/widget_advance_search_table.xml diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.kt b/app/src/main/java/com/hippo/ehviewer/Settings.kt index c15fbbc91..6e07bdda9 100755 --- a/app/src/main/java/com/hippo/ehviewer/Settings.kt +++ b/app/src/main/java/com/hippo/ehviewer/Settings.kt @@ -2,6 +2,7 @@ package com.hippo.ehviewer +import com.hippo.ehviewer.client.EhUtils import com.hippo.ehviewer.client.data.FavListUrlBuilder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -59,6 +60,7 @@ object Settings : DataStorePreferences(null) { var securityDelay by intPref("require_unlock_delay", 0) var clipboardTextHashCode by intPref("clipboard_text_hash_code", 0) var listThumbSize by intPref("list_tile_size", 40) + var searchCategory by intPref("search_pref", EhUtils.ALL_CATEGORY) var requestNewsTimerHour by intPref("request_news_timer_hour", -1) var requestNewsTimerMinute by intPref("request_news_timer_minute", -1) var dataMapNextId by intPref("data_map_next_id", 0) @@ -66,7 +68,6 @@ object Settings : DataStorePreferences(null) { var defaultDownloadLabel by stringOrNullPref("default_download_label", null) var displayName by stringOrNullPref("display_name", null) var avatar by stringOrNullPref("avatar", null) - var dohUrl by stringPref("doh_url", "") var language by stringPref("app_language", "system").observed { updateWhenLocaleChanges() } var lastDawnDay by longPref("last_dawn_day", 0) diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/AdvanceSearchTable.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/AdvanceSearchTable.kt index 9c0e3386d..da8a76c31 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/AdvanceSearchTable.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/AdvanceSearchTable.kt @@ -15,132 +15,7 @@ */ package com.hippo.ehviewer.ui.legacy -import android.content.Context -import android.util.AttributeSet -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.AutoCompleteTextView -import android.widget.CheckBox -import android.widget.CompoundButton -import android.widget.EditText -import android.widget.LinearLayout -import android.widget.TextView -import com.google.android.material.textfield.TextInputLayout -import com.hippo.ehviewer.R -import com.hippo.ehviewer.yorozuya.toIntOrDefault - -class AdvanceSearchTable @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, -) : LinearLayout(context, attrs) { - private var mSh: CheckBox - private var mSto: CheckBox - private var mMinRating: TextInputLayout - private var mSp: CheckBox - private var mSpf: EditText - private var mSpt: EditText - private var mSfl: CheckBox - private var mSfu: CheckBox - private var mSft: CheckBox - private val mArray: Array = context.resources.getStringArray(R.array.search_min_rating) - - init { - orientation = VERTICAL - val inflater = LayoutInflater.from(context) - inflater.inflate(R.layout.widget_advance_search_table, this) - val row0 = getChildAt(0) as ViewGroup - mSh = row0.getChildAt(0) as CheckBox - mSto = row0.getChildAt(1) as CheckBox - val row1 = getChildAt(1) as ViewGroup - mMinRating = row1.getChildAt(0) as TextInputLayout - val row2 = getChildAt(2) as ViewGroup - mSp = row2.getChildAt(0) as CheckBox - mSpf = row2.getChildAt(1) as EditText - mSpt = row2.getChildAt(3) as EditText - val row4 = getChildAt(4) as ViewGroup - mSfl = row4.getChildAt(0) as CheckBox - mSfu = row4.getChildAt(1) as CheckBox - mSft = row4.getChildAt(2) as CheckBox - - mSpt.setOnEditorActionListener { v: TextView, _: Int, _: KeyEvent? -> - val nextView = v.focusSearch( - FOCUS_DOWN, - ) - nextView?.requestFocus(FOCUS_DOWN) - true - } - - mSpt.isEnabled = mSp.isChecked - mSpf.isEnabled = mSp.isChecked - mSp.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean -> - mSpt.isEnabled = isChecked - mSpf.isEnabled = isChecked - } - } - - var advanceSearch: Int - get() { - var advanceSearch = 0 - if (mSh.isChecked) advanceSearch = advanceSearch or SH - if (mSto.isChecked) advanceSearch = advanceSearch or STO - if (mSfl.isChecked) advanceSearch = advanceSearch or SFL - if (mSfu.isChecked) advanceSearch = advanceSearch or SFU - if (mSft.isChecked) advanceSearch = advanceSearch or SFT - return advanceSearch - } - set(advanceSearch) { - mSh.isChecked = advanceSearch and SH != 0 - mSto.isChecked = advanceSearch and STO != 0 - mSfl.isChecked = advanceSearch and SFL != 0 - mSfu.isChecked = advanceSearch and SFU != 0 - mSft.isChecked = advanceSearch and SFT != 0 - } - var minRating: Int - get() { - val position = mArray.indexOf(mMinRating.editText!!.text.toString()) - return if (position > 0) { - position + 1 - } else { - -1 - } - } - set(minRating) { - (mMinRating.editText!! as AutoCompleteTextView).setText( - mArray[if (minRating in 2..5) minRating - 1 else 0], - false, - ) - } - var pageFrom: Int - get() = if (mSp.isChecked) { - mSpf.text.toString().toIntOrDefault(-1) - } else { - -1 - } - set(pageFrom) { - if (pageFrom > 0) { - mSpf.setText(pageFrom.toString()) - mSp.isChecked = true - } else { - mSp.isChecked = false - mSpf.text = null - } - } - var pageTo: Int - get() = if (mSp.isChecked) { - mSpt.text.toString().toIntOrDefault(-1) - } else { - -1 - } - set(pageTo) { - if (pageTo > 0) { - mSpt.setText(pageTo.toString()) - mSp.isChecked = true - } else { - mSp.isChecked = false - } - } - +class AdvanceSearchTable { companion object { const val SH = 0x1 const val STO = 0x2 diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/ImageSearchLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/ImageSearchLayout.kt deleted file mode 100644 index 9669e723f..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/ImageSearchLayout.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.hippo.ehviewer.ui.legacy - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.ImageDecoder -import android.net.Uri -import android.util.AttributeSet -import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageOnly -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn -import androidx.compose.material3.Card -import androidx.compose.material3.Checkbox -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ProvideTextStyle -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.AbstractComposeView -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.google.accompanist.themeadapter.material3.Mdc3Theme -import com.hippo.ehviewer.AppConfig -import com.hippo.ehviewer.R -import com.hippo.ehviewer.client.data.ListUrlBuilder -import com.hippo.ehviewer.client.exception.EhException -import com.hippo.ehviewer.image.Image -import com.hippo.ehviewer.util.pickVisualMedia -import com.hippo.unifile.UniFile -import kotlinx.coroutines.launch - -class ImageSearchLayout @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0, -) : AbstractComposeView(context, attrs, defStyle) { - private var uss by mutableStateOf(false) - private var osc by mutableStateOf(false) - private var path by mutableStateOf("") - - @Composable - override fun Content() { - Mdc3Theme { - val coroutineScope = rememberCoroutineScope() - fun selectImage() = coroutineScope.launch { - context.pickVisualMedia(ImageOnly)?.let { - path = it.toString() - } - } - - Column(modifier = Modifier.fillMaxWidth()) { - AnimatedVisibility(visible = path.isNotBlank(), modifier = Modifier.align(Alignment.CenterHorizontally)) { - Card { - val maxSize = dimensionResource(id = R.dimen.image_search_max_size) - AsyncImage( - model = path, - contentDescription = null, - modifier = Modifier.sizeIn(maxWidth = maxSize, maxHeight = maxSize), - ) - } - } - FilledTonalButton( - onClick = ::selectImage, - modifier = Modifier.fillMaxWidth().padding(8.dp), - ) { - Text(text = stringResource(id = R.string.select_image)) - } - ProvideTextStyle(MaterialTheme.typography.bodyMedium) { - Row { - Row(modifier = Modifier.weight(1f)) { - Checkbox(checked = uss, onCheckedChange = { uss = it }) - Text(text = stringResource(id = R.string.search_uss), modifier = Modifier.align(Alignment.CenterVertically)) - } - Row(modifier = Modifier.weight(1f)) { - Checkbox(checked = osc, onCheckedChange = { osc = it }) - Text(text = stringResource(id = R.string.search_osc), modifier = Modifier.align(Alignment.CenterVertically)) - } - } - } - } - } - } - - fun formatListUrlBuilder(lub: ListUrlBuilder) { - if (path.isBlank()) throw EhException(context.getString(R.string.select_image_first)) - val uri = Uri.parse(path) - val src = UniFile.fromUri(context, uri)?.imageSource ?: return - val temp = AppConfig.createTempFile() ?: return - val bitmap = ImageDecoder.decodeBitmap(src, Image.imageSearchDecoderSampleListener) - temp.outputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) } - lub.imagePath = temp.path - lub.isUseSimilarityScan = uss - lub.isOnlySearchCovers = osc - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/RadioGridGroup.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/RadioGridGroup.kt deleted file mode 100644 index 4c7df0e77..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/RadioGridGroup.kt +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hippo.ehviewer.ui.legacy - -import android.content.Context -import android.util.AttributeSet -import android.view.View -import android.widget.CompoundButton -import android.widget.RadioButton -import androidx.annotation.IdRes - -class RadioGridGroup : SimpleGridLayout { - /** - * - * Returns the identifier of the selected radio button in this group. - * Upon empty selection, the returned value is -1. - * - * @return the unique id of the selected radio button in this group - * @see .check - * @see .clearCheck - */ - // holds the checked id; the selection is empty by default - @get:IdRes - var checkedRadioButtonId = -1 - private set - - // tracks children radio buttons checked state - private var mChildOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null - - // when true, mOnCheckedChangeListener discards events - private var mProtectFromCheckedChange = false - private var mOnCheckedChangeListener: OnCheckedChangeListener? = null - private var mPassThroughListener: PassThroughHierarchyChangeListener? = null - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - init(context, attrs) - } - - constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( - context, - attrs, - defStyle, - ) { - init(context, attrs) - } - - private fun init(context: Context, attrs: AttributeSet?) { - val a = context.obtainStyledAttributes(attrs, RADIO_ATTRS) - val value = a.getResourceId(0, NO_ID) - if (value != NO_ID) { - checkedRadioButtonId = value - } - a.recycle() - mChildOnCheckedChangeListener = CheckedStateTracker() - mPassThroughListener = PassThroughHierarchyChangeListener() - super.setOnHierarchyChangeListener(mPassThroughListener) - } - - /** - * {@inheritDoc} - */ - override fun setOnHierarchyChangeListener(listener: OnHierarchyChangeListener) { - // the user listener is delegated to our pass-through listener - mPassThroughListener!!.mOnHierarchyChangeListener = listener - } - - /** - * {@inheritDoc} - */ - override fun onFinishInflate() { - super.onFinishInflate() - - // checks the appropriate radio button as requested in the XML file - if (checkedRadioButtonId != -1) { - mProtectFromCheckedChange = true - setCheckedStateForView(checkedRadioButtonId, true) - mProtectFromCheckedChange = false - setCheckedId(checkedRadioButtonId) - } - } - - override fun addView(child: View, index: Int, params: LayoutParams) { - if (child is RadioButton) { - if (child.isChecked) { - mProtectFromCheckedChange = true - if (checkedRadioButtonId != -1) { - setCheckedStateForView(checkedRadioButtonId, false) - } - mProtectFromCheckedChange = false - setCheckedId(child.getId()) - } - } - super.addView(child, index, params) - } - - /** - * - * Sets the selection to the radio button whose identifier is passed in - * parameter. Using -1 as the selection identifier clears the selection; - * such an operation is equivalent to invoking [.clearCheck]. - * - * @param id the unique id of the radio button to select in this group - * @see .getCheckedRadioButtonId - * @see .clearCheck - */ - fun check(@IdRes id: Int) { - // don't even bother - if (id != -1 && id == checkedRadioButtonId) { - return - } - if (checkedRadioButtonId != -1) { - setCheckedStateForView(checkedRadioButtonId, false) - } - if (id != -1) { - setCheckedStateForView(id, true) - } - setCheckedId(id) - } - - private fun setCheckedId(@IdRes id: Int) { - checkedRadioButtonId = id - if (mOnCheckedChangeListener != null) { - mOnCheckedChangeListener!!.onCheckedChanged(this, checkedRadioButtonId) - } - } - - private fun setCheckedStateForView(viewId: Int, checked: Boolean) { - val checkedView = findViewById(viewId) - if (checkedView is RadioButton) { - checkedView.isChecked = checked - } - } - - /** - * - * Clears the selection. When the selection is cleared, no radio button - * in this group is selected and [.getCheckedRadioButtonId] returns - * null. - * - * @see .check - * @see .getCheckedRadioButtonId - */ - fun clearCheck() { - check(-1) - } - - /** - * - * Register a callback to be invoked when the checked radio button - * changes in this group. - * - * @param listener the callback to call on checked state change - */ - fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) { - mOnCheckedChangeListener = listener - } - - /** - * - * Interface definition for a callback to be invoked when the checked - * radio button changed in this group. - */ - interface OnCheckedChangeListener { - /** - * - * Called when the checked radio button has changed. When the - * selection is cleared, checkedId is -1. - * - * @param group the group in which the checked radio button has changed - * @param checkedId the unique identifier of the newly checked radio button - */ - fun onCheckedChanged(group: RadioGridGroup?, @IdRes checkedId: Int) - } - - private inner class CheckedStateTracker : CompoundButton.OnCheckedChangeListener { - override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { - // prevents from infinite recursion - if (mProtectFromCheckedChange) { - return - } - mProtectFromCheckedChange = true - if (checkedRadioButtonId != -1) { - setCheckedStateForView(checkedRadioButtonId, false) - } - mProtectFromCheckedChange = false - val id = buttonView.id - setCheckedId(id) - } - } - - /** - * - * A pass-through listener acts upon the events and dispatches them - * to another listener. This allows the table layout to set its own internal - * hierarchy change listener without preventing the user to setup his. - */ - private inner class PassThroughHierarchyChangeListener : OnHierarchyChangeListener { - var mOnHierarchyChangeListener: OnHierarchyChangeListener? = null - - /** - * {@inheritDoc} - */ - override fun onChildViewAdded(parent: View, child: View) { - if (parent === this@RadioGridGroup && child is RadioButton) { - var id = child.getId() - // generates an id if it's missing - if (id == NO_ID) { - id = generateViewId() - child.setId(id) - } - child.setOnCheckedChangeListener(mChildOnCheckedChangeListener) - } - mOnHierarchyChangeListener?.onChildViewAdded(parent, child) - } - - /** - * {@inheritDoc} - */ - override fun onChildViewRemoved(parent: View, child: View) { - if (parent === this@RadioGridGroup && child is RadioButton) { - child.setOnCheckedChangeListener(null) - } - mOnHierarchyChangeListener?.onChildViewRemoved(parent, child) - } - } - - companion object { - private val RADIO_ATTRS = intArrayOf(android.R.attr.checkedButton) - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt index d7ce604d8..cd622fb38 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt @@ -1,375 +1,167 @@ -/* - * Copyright 2022 Tarsin Norbin - * - * This file is part of EhViewer - * - * EhViewer is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * EhViewer is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with EhViewer. - * If not, see . - */ - package com.hippo.ehviewer.ui.legacy -import android.annotation.SuppressLint import android.content.Context -import android.content.SharedPreferences +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.net.Uri import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.CompoundButton -import androidx.annotation.IntDef -import androidx.core.content.edit -import androidx.core.view.forEach -import androidx.recyclerview.widget.DefaultItemAnimator -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.chip.Chip -import com.google.android.material.tabs.TabLayout -import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.AbstractComposeView +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import com.google.accompanist.themeadapter.material3.Mdc3Theme +import com.hippo.ehviewer.AppConfig import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings -import com.hippo.ehviewer.client.EhUtils import com.hippo.ehviewer.client.data.ListUrlBuilder import com.hippo.ehviewer.client.exception.EhException -import com.hippo.ehviewer.databinding.SearchAdvanceBinding -import com.hippo.ehviewer.databinding.SearchCategoryBinding -import com.hippo.ehviewer.databinding.SearchNormalBinding -import com.hippo.ehviewer.yorozuya.removeFromParent +import com.hippo.ehviewer.image.Image +import com.hippo.ehviewer.ui.main.ImageSearch +import com.hippo.ehviewer.ui.main.NormalSearch +import com.hippo.ehviewer.util.pickVisualMedia +import com.hippo.unifile.UniFile +import kotlinx.coroutines.launch -@SuppressLint("InflateParams") class SearchLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, -) : EasyRecyclerView(context, attrs), - CompoundButton.OnCheckedChangeListener, - View.OnClickListener, - OnTabSelectedListener { + defStyle: Int = 0, +) : AbstractComposeView(context, attrs, defStyle) { + private var isNormalMode by mutableStateOf(true) // else ImageSearch mode + private var isAdvancedMode by mutableStateOf(false) - @SearchMode - private var mSearchMode = SEARCH_MODE_NORMAL - private var mEnableAdvance = false - private val binding: SearchNormalBinding - private val advanceBinding: SearchAdvanceBinding - private val mImageView: ImageSearchLayout - private val mActionView: View - private val mAction: TabLayout - private val mAdapter: SearchAdapter - private var mHelper: Helper? = null - private val mSharePref: SharedPreferences = Settings.prefs - private val mInflater: LayoutInflater + private var mCategory by mutableIntStateOf(Settings.searchCategory) + private var mSearchMode by mutableIntStateOf(1) - init { - mInflater = LayoutInflater.from(context) - val resources = context.resources - layoutManager = SearchLayoutManager(context) - mAdapter = SearchAdapter() - mAdapter.setHasStableIds(true) - adapter = mAdapter - setHasFixedSize(true) - clipToPadding = false - (itemAnimator as DefaultItemAnimator?)?.supportsChangeAnimations = false - val interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval) - val paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h) - val paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v) - val decoration = MarginItemDecoration( - interval, - paddingH, - paddingV, - paddingH, - paddingV, - ) - addItemDecoration(decoration) - decoration.applyPaddings(this) - // Create normal view - binding = SearchNormalBinding.inflate(mInflater) - val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhUtils.ALL_CATEGORY) - for (mPair in mCategoryTable) { - val mChip = inflate(context, R.layout.filter_chip, null) as IdentifiedChip - mChip.isCheckable = true - mChip.setText(mPair.second) - mChip.idt = mPair.first - mChip.isChecked = mPair.first and mCategoryStored != 0 - mChip.setOnLongClickListener { - if (mChip.isChecked) { - binding.searchCategoryChipgroup.forEach { - (it as IdentifiedChip).isChecked = true + private var uss by mutableStateOf(false) + private var osc by mutableStateOf(false) + private var path by mutableStateOf("") + + @Composable + override fun Content() { + val coroutineScope = rememberCoroutineScope() + fun selectImage() = coroutineScope.launch { + context.pickVisualMedia(ActivityResultContracts.PickVisualMedia.ImageOnly)?.let { + path = it.toString() + } + } + Mdc3Theme { + Column( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.search_layout_margin_h)), + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.search_layout_margin_v)), + ) { + AnimatedVisibility(visible = isNormalMode) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.search_category_padding_h), vertical = dimensionResource(id = R.dimen.search_category_padding_v))) { + Text(text = stringResource(id = R.string.search_normal), modifier = Modifier.height(dimensionResource(id = R.dimen.search_category_title_height)), style = MaterialTheme.typography.titleMedium) + NormalSearch( + category = mCategory, + onCategoryChanged = { + Settings.searchCategory = it + mCategory = it + }, + searchMode = mSearchMode, + onSearchModeChanged = { mSearchMode = it }, + isAdvanced = isAdvancedMode, + onAdvancedChanged = { isAdvancedMode = it }, + showInfo = { BaseDialogBuilder(context).setMessage(R.string.search_tip).show() }, + ) + } } - mChip.isChecked = false - } else { - binding.searchCategoryChipgroup.clearCheck() - mChip.isChecked = true } - true + AnimatedVisibility(visible = isNormalMode && isAdvancedMode) { + ElevatedCard { + } + } + AnimatedVisibility(visible = !isNormalMode) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.search_category_padding_h), vertical = dimensionResource(id = R.dimen.search_category_padding_v))) { + Text(text = stringResource(id = R.string.search_image), modifier = Modifier.height(dimensionResource(id = R.dimen.search_category_title_height)), style = MaterialTheme.typography.titleMedium) + ImageSearch( + imagePath = path, + onSelectImage = ::selectImage, + uss = uss, + onUssChecked = { uss = it }, + osc = osc, + onOscChecked = { osc = it }, + ) + } + } + } + TabRow( + selectedTabIndex = if (isNormalMode) 0 else 1, + divider = {}, + ) { + Tab( + selected = isNormalMode, + onClick = { isNormalMode = true }, + text = { Text(text = stringResource(id = R.string.keyword_search)) }, + ) + Tab( + selected = !isNormalMode, + onClick = { isNormalMode = false }, + text = { Text(text = stringResource(id = R.string.search_image)) }, + ) + } } - binding.searchCategoryChipgroup.addView(mChip) } - binding.searchCategoryChipgroup.setOnCheckedStateChangeListener { group, checkedIds -> - var mCategory = 0 - for (index in checkedIds) { - mCategory = mCategory or group.findViewById(index).idt - } - mSharePref.edit { putInt(SEARCH_CATEGORY_PREF, mCategory) } - } - binding.normalSearchModeHelp.setOnClickListener(this) - binding.searchEnableAdvance.setOnCheckedChangeListener(this) - // Create advance view - advanceBinding = SearchAdvanceBinding.inflate(mInflater) - // Create image view - mImageView = mInflater.inflate(R.layout.search_image, null) as ImageSearchLayout - // Create action view - mActionView = mInflater.inflate(R.layout.search_action, null) - mActionView.layoutParams = LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - mAction = mActionView.findViewById(R.id.action) - mAction.addOnTabSelectedListener(this) } - fun setHelper(helper: Helper?) { - mHelper = helper - } - - fun scrollSearchContainerToTop() { - (layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0) - } - - fun setNormalSearchMode(id: Int) { - binding.normalSearchMode.check(id) + fun setSearchMyTags(isMyTags: Boolean) { + if (isMyTags) mSearchMode = 2 } fun setCategory(category: Int) { - binding.searchCategoryChipgroup.forEach { - (it as IdentifiedChip).apply { - isChecked = idt and category != 0 - } - } + mCategory = category } - override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { - if (buttonView === binding.searchEnableAdvance) { - post { - mEnableAdvance = isChecked - if (mSearchMode == SEARCH_MODE_NORMAL) { - if (mEnableAdvance) { - mAdapter.notifyItemInserted(1) - } else { - mAdapter.notifyItemRemoved(1) - } - if (mHelper != null) { - mHelper!!.onChangeSearchMode() - } - } - } - } - } - - @SuppressLint("NonConstantResourceId") fun formatListUrlBuilder(urlBuilder: ListUrlBuilder, query: String?) { urlBuilder.reset() - when (mSearchMode) { - SEARCH_MODE_NORMAL -> { - when (binding.normalSearchMode.checkedRadioButtonId) { - R.id.search_subscription_search -> { - urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION - } - - R.id.search_specify_uploader -> { - urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER - } - - R.id.search_specify_tag -> { - urlBuilder.mode = ListUrlBuilder.MODE_TAG - } - - else -> { - urlBuilder.mode = ListUrlBuilder.MODE_NORMAL - } + when (isNormalMode) { + true -> { + when (mSearchMode) { + 1 -> urlBuilder.mode = ListUrlBuilder.MODE_NORMAL + 2 -> urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION + 3 -> urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER + 4 -> urlBuilder.mode = ListUrlBuilder.MODE_TAG } urlBuilder.keyword = query - urlBuilder.category = Settings.prefs.getInt( - SEARCH_CATEGORY_PREF, - 0, - ) - if (mEnableAdvance) { - urlBuilder.advanceSearch = advanceBinding.searchAdvanceSearchTable.advanceSearch - urlBuilder.minRating = advanceBinding.searchAdvanceSearchTable.minRating - val pageFrom = advanceBinding.searchAdvanceSearchTable.pageFrom - val pageTo = advanceBinding.searchAdvanceSearchTable.pageTo - if (pageTo != -1 && pageTo < 10) { - throw EhException(context.getString(R.string.search_sp_err1)) - } else if (pageFrom != -1 && pageTo != -1 && pageTo - pageFrom < 20) { - throw EhException(context.getString(R.string.search_sp_err2)) - } - urlBuilder.pageFrom = pageFrom - urlBuilder.pageTo = pageTo - } + urlBuilder.category = mCategory } - SEARCH_MODE_IMAGE -> { + false -> { urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH - mImageView.formatListUrlBuilder(urlBuilder) + if (path.isBlank()) throw EhException(context.getString(R.string.select_image_first)) + val uri = Uri.parse(path) + val src = UniFile.fromUri(context, uri)?.imageSource ?: return + val temp = AppConfig.createTempFile() ?: return + val bitmap = ImageDecoder.decodeBitmap(src, Image.imageSearchDecoderSampleListener) + temp.outputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) } + urlBuilder.imagePath = temp.path + urlBuilder.isUseSimilarityScan = uss + urlBuilder.isOnlySearchCovers = osc } } } - override fun onClick(v: View) { - if (binding.normalSearchModeHelp === v) { - BaseDialogBuilder(context) - .setMessage(R.string.search_tip) - .show() - } - } - - override fun onTabSelected(tab: TabLayout.Tab) { - post { setSearchMode(tab.position) } - } - - private fun setSearchMode(@SearchMode mode: Int) { - val oldItemCount = mAdapter.itemCount - mSearchMode = mode - val newItemCount = mAdapter.itemCount - mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1) - mAdapter.notifyItemRangeInserted(0, newItemCount - 1) - if (mHelper != null) { - mHelper!!.onChangeSearchMode() - } - } - - override fun onTabUnselected(tab: TabLayout.Tab) {} - override fun onTabReselected(tab: TabLayout.Tab) {} - - @IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE) - @Retention(AnnotationRetention.SOURCE) - private annotation class SearchMode - interface Helper { - fun onChangeSearchMode() - } - - internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context!!) { - override fun onLayoutChildren(recycler: Recycler, state: State) { - try { - super.onLayoutChildren(recycler, state) - } catch (e: IndexOutOfBoundsException) { - e.printStackTrace() - } - } - } - - private inner class SimpleHolder(itemView: View) : ViewHolder(itemView) - - private inner class SearchAdapter : Adapter() { - override fun getItemCount(): Int { - var count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode] - if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) { - count-- - } - return count - } - - override fun getItemViewType(position: Int): Int { - var type = SEARCH_ITEM_TYPE[mSearchMode][position] - if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) { - type = ITEM_TYPE_ACTION - } - return type - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder { - val view: View - if (viewType == ITEM_TYPE_ACTION) { - mActionView.removeFromParent() - view = mActionView - } else { - view = mInflater.inflate(R.layout.search_category, parent, false) - SearchCategoryBinding.bind(view).run { - when (viewType) { - ITEM_TYPE_NORMAL -> { - categoryTitle.setText(R.string.search_normal) - binding.root.removeFromParent() - categoryContent.addView(binding.root) - } - - ITEM_TYPE_NORMAL_ADVANCE -> { - categoryTitle.setText(R.string.search_advance) - advanceBinding.root.removeFromParent() - categoryContent.addView(advanceBinding.root) - } - - ITEM_TYPE_IMAGE -> { - categoryTitle.setText(R.string.search_image) - mImageView.removeFromParent() - categoryContent.addView(mImageView) - } - } - } - } - return SimpleHolder(view) - } - - override fun onBindViewHolder(holder: SimpleHolder, position: Int) { - if (holder.itemViewType == ITEM_TYPE_ACTION) { - mAction.selectTab(mAction.getTabAt(mSearchMode)) - } - } - - override fun getItemId(position: Int): Long { - var type = SEARCH_ITEM_TYPE[mSearchMode][position] - if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) { - type = ITEM_TYPE_ACTION - } - return type.toLong() - } - } - - companion object { - const val SEARCH_MODE_NORMAL = 0 - const val SEARCH_MODE_IMAGE = 1 - const val SEARCH_CATEGORY_PREF = "search_pref" - private const val ITEM_TYPE_NORMAL = 0 - private const val ITEM_TYPE_NORMAL_ADVANCE = 1 - private const val ITEM_TYPE_IMAGE = 2 - private const val ITEM_TYPE_ACTION = 3 - private val SEARCH_ITEM_COUNT_ARRAY = intArrayOf( - 3, - 2, - ) - private val mCategoryTable: ArrayList> = arrayListOf( - Pair(EhUtils.DOUJINSHI, R.string.doujinshi), - Pair(EhUtils.MANGA, R.string.manga), - Pair(EhUtils.ARTIST_CG, R.string.artist_cg), - Pair(EhUtils.GAME_CG, R.string.game_cg), - Pair(EhUtils.WESTERN, R.string.western), - Pair(EhUtils.NON_H, R.string.non_h), - Pair(EhUtils.IMAGE_SET, R.string.image_set), - Pair(EhUtils.COSPLAY, R.string.cosplay), - Pair(EhUtils.ASIAN_PORN, R.string.asian_porn), - Pair(EhUtils.MISC, R.string.misc), - ) - private val SEARCH_ITEM_TYPE = arrayOf( - intArrayOf(ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION), - intArrayOf( - ITEM_TYPE_IMAGE, - ITEM_TYPE_ACTION, - ), - ) + fun setHelper(helper: () -> Unit) { } } - -class IdentifiedChip @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, -) : Chip( - context, - attrs, -) { - var idt = 0 -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleGridLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleGridLayout.kt deleted file mode 100644 index 940b72bf1..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleGridLayout.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hippo.ehviewer.ui.legacy - -import android.content.Context -import android.util.AttributeSet -import android.view.ViewGroup -import com.hippo.ehviewer.R - -/** - * not scrollable - * - * @author Hippo - */ -open class SimpleGridLayout : ViewGroup { - private var mColumnCount = 0 - private var mItemMargin = 0 - private var mRowHeights: IntArray? = null - private var mItemWidth = 0 - - constructor(context: Context) : super(context) { - init(context, null) - } - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - init(context, attrs) - } - - constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( - context, - attrs, - defStyle, - ) { - init(context, attrs) - } - - private fun init(context: Context, attrs: AttributeSet?) { - val a = context.obtainStyledAttributes(attrs, R.styleable.SimpleGridLayout) - mColumnCount = a.getInteger(R.styleable.SimpleGridLayout_columnCount, DEFAULT_COLUMN_COUNT) - mItemMargin = a.getDimensionPixelOffset(R.styleable.SimpleGridLayout_itemMargin, 0) - a.recycle() - } - - fun setItemMargin(itemMargin: Int) { - if (mItemMargin != itemMargin) { - mItemMargin = itemMargin - requestLayout() - } - } - - fun setColumnCount(columnCount: Int) { - check(columnCount > 0) { "Column count can't be $columnCount" } - if (mColumnCount != columnCount) { - mColumnCount = columnCount - requestLayout() - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val maxRowCount = (childCount + mColumnCount - 1) / mColumnCount - if (mRowHeights == null || mRowHeights!!.size != maxRowCount) { - mRowHeights = IntArray(maxRowCount) - } - val widthMode = MeasureSpec.getMode(widthMeasureSpec) - val heightMode = MeasureSpec.getMode(heightMeasureSpec) - var maxWidth = MeasureSpec.getSize(widthMeasureSpec) - var maxHeight = MeasureSpec.getSize(heightMeasureSpec) - if (widthMode == MeasureSpec.UNSPECIFIED) { - maxWidth = 300 - } - if (heightMode == MeasureSpec.UNSPECIFIED) { - maxHeight = Int.MAX_VALUE and (0x3 shl 30).inv() - } - - // Get item width MeasureSpec - mItemWidth = - ((maxWidth - paddingLeft - paddingRight - (mColumnCount - 1) * mItemMargin) / mColumnCount) - .coerceAtLeast(1) - val itemWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mItemWidth, MeasureSpec.EXACTLY) - val itemHeightMeasureSpec = MeasureSpec.UNSPECIFIED - val measuredWidth = maxWidth - var measuredHeight = 0 - var rowHeight = 0 - var row = 0 - val count = childCount - var index = 0 - var indexInRow = 0 - while (index < count) { - val child = getChildAt(index) - if (child.visibility == GONE) { - indexInRow-- - index++ - indexInRow++ - continue - } - child.measure(itemWidthMeasureSpec, itemHeightMeasureSpec) - if (indexInRow == mColumnCount) { - // New row - indexInRow = 0 - rowHeight = 0 - row++ - } - rowHeight = rowHeight.coerceAtLeast(child.measuredHeight) - if (indexInRow == mColumnCount - 1 || index == count - 1) { - mRowHeights!![row] = rowHeight - measuredHeight += rowHeight + mItemMargin - } - index++ - indexInRow++ - } - measuredHeight -= mItemMargin - measuredHeight = (measuredHeight + paddingTop + paddingBottom).coerceIn(0, maxHeight) - setMeasuredDimension(measuredWidth, measuredHeight) - } - - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - val itemWidth = mItemWidth - val itemMargin = mItemMargin - val paddingLeft = paddingLeft - var left = paddingLeft - var top = paddingTop - var row = 0 - val count = childCount - var index = 0 - var indexInRow = 0 - while (index < count) { - val child = getChildAt(index) - if (child.visibility == GONE) { - indexInRow-- - index++ - indexInRow++ - continue - } - if (indexInRow == mColumnCount) { - // New row - left = paddingLeft - top += mRowHeights!![row] + itemMargin - indexInRow = 0 - row++ - } - child.layout(left, top, left + child.measuredWidth, top + child.measuredHeight) - left += itemWidth + itemMargin - index++ - indexInRow++ - } - } - - companion object { - private const val DEFAULT_COLUMN_COUNT = 3 - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/main/ImageSearch.kt b/app/src/main/java/com/hippo/ehviewer/ui/main/ImageSearch.kt new file mode 100644 index 000000000..2e28fc304 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/main/ImageSearch.kt @@ -0,0 +1,61 @@ +package com.hippo.ehviewer.ui.main + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material3.Card +import androidx.compose.material3.Checkbox +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.hippo.ehviewer.R + +@Composable +fun ImageSearch( + imagePath: String, + onSelectImage: () -> Unit, + uss: Boolean, + onUssChecked: (Boolean) -> Unit, + osc: Boolean, + onOscChecked: (Boolean) -> Unit, +) = Column(modifier = Modifier.fillMaxWidth()) { + AnimatedVisibility(visible = imagePath.isNotBlank(), modifier = Modifier.align(Alignment.CenterHorizontally)) { + Card { + val maxSize = dimensionResource(id = R.dimen.image_search_max_size) + AsyncImage( + model = imagePath, + contentDescription = null, + modifier = Modifier.sizeIn(maxWidth = maxSize, maxHeight = maxSize), + ) + } + } + FilledTonalButton( + onClick = onSelectImage, + modifier = Modifier.fillMaxWidth().padding(8.dp), + ) { + Text(text = stringResource(id = R.string.select_image)) + } + ProvideTextStyle(MaterialTheme.typography.bodyMedium) { + Row { + Row(modifier = Modifier.weight(1f)) { + Checkbox(checked = uss, onCheckedChange = onUssChecked) + Text(text = stringResource(id = R.string.search_uss), modifier = Modifier.align(Alignment.CenterVertically)) + } + Row(modifier = Modifier.weight(1f)) { + Checkbox(checked = osc, onCheckedChange = onOscChecked) + Text(text = stringResource(id = R.string.search_osc), modifier = Modifier.align(Alignment.CenterVertically)) + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/main/NormalSearch.kt b/app/src/main/java/com/hippo/ehviewer/ui/main/NormalSearch.kt new file mode 100644 index 000000000..5ed7e9e9b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/main/NormalSearch.kt @@ -0,0 +1,100 @@ +package com.hippo.ehviewer.ui.main + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Help +import androidx.compose.material3.Checkbox +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.hippo.ehviewer.R +import com.hippo.ehviewer.client.EhUtils + +private val categoryTable = arrayOf( + EhUtils.DOUJINSHI to R.string.doujinshi, + EhUtils.MANGA to R.string.manga, + EhUtils.ARTIST_CG to R.string.artist_cg, + EhUtils.GAME_CG to R.string.game_cg, + EhUtils.WESTERN to R.string.western, + EhUtils.NON_H to R.string.non_h, + EhUtils.IMAGE_SET to R.string.image_set, + EhUtils.COSPLAY to R.string.cosplay, + EhUtils.ASIAN_PORN to R.string.asian_porn, + EhUtils.MISC to R.string.misc, +) + +private val modeTable = arrayOf( + 1 to R.string.search_normal_search, + 2 to R.string.search_subscription_search, + 3 to R.string.search_specify_uploader, + 4 to R.string.search_specify_tag, +) + +@Composable +fun NormalSearch( + category: Int, + onCategoryChanged: (Int) -> Unit, + searchMode: Int, + onSearchModeChanged: (Int) -> Unit, + isAdvanced: Boolean, + onAdvancedChanged: (Boolean) -> Unit, + showInfo: () -> Unit, +) { + FlowRow(modifier = Modifier.wrapContentHeight(align = Alignment.Top)) { + categoryTable.forEach { + val selected = category and it.first != 0 + FilterChip( + selected = selected, + onClick = { onCategoryChanged(if (selected) category xor it.first else category or it.first) }, + label = { Text(text = stringResource(id = it.second)) }, + leadingIcon = { if (selected) Icon(imageVector = Icons.Default.Check, contentDescription = null) }, + modifier = Modifier.padding(horizontal = 4.dp).align(alignment = Alignment.CenterVertically), + ) + } + } + ProvideTextStyle(MaterialTheme.typography.bodyMedium) { + FlowRow { + modeTable.forEach { + Row( + modifier = Modifier.weight(1F).width(112.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton( + selected = (it.first == searchMode), + onClick = { onSearchModeChanged(it.first) }, + ) + Text( + text = stringResource(id = it.second), + ) + } + } + } + Row(horizontalArrangement = Arrangement.Start) { + Box(modifier = Modifier.weight(1f)) { + IconButton(onClick = showInfo) { + Icon(imageVector = Icons.Default.Help, contentDescription = null) + } + } + Row(modifier = Modifier.weight(1f)) { + Checkbox(checked = isAdvanced, onCheckedChange = onAdvancedChanged) + Text(text = stringResource(id = R.string.search_enable_advance), modifier = Modifier.align(Alignment.CenterVertically)) + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt index fb4e7341e..6c77b3bb4 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt @@ -89,7 +89,6 @@ import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener import com.hippo.ehviewer.ui.legacy.FabLayout.OnExpandListener import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener import com.hippo.ehviewer.ui.legacy.GalleryInfoContentHelper -import com.hippo.ehviewer.ui.legacy.SearchLayout import com.hippo.ehviewer.ui.legacy.ViewTransition import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper import com.hippo.ehviewer.ui.setMD3Content @@ -109,7 +108,7 @@ import java.time.ZoneId import java.time.ZoneOffset import com.hippo.ehviewer.download.DownloadManager as downloadManager -class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.Helper, OnClickFabListener, OnExpandListener { +class GalleryListScene : SearchBarScene(), OnDragHandlerListener, OnClickFabListener, OnExpandListener { private val mCallback = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { when (mState) { @@ -309,7 +308,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H binding.fabLayout.setSecondaryFabVisibilityAt(2, !isPopular) // Update normal search mode and category - binding.searchLayout.setNormalSearchMode(if (mode == MODE_SUBSCRIPTION) R.id.search_subscription_search else R.id.search_normal_search) + binding.searchLayout.setSearchMyTags(mode == MODE_SUBSCRIPTION) if (category != EhUtils.NONE) { binding.searchLayout.setCategory(category) } @@ -378,7 +377,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H } setSearchBarHint() setSearchBarSuggestionProvider() - binding.searchLayout.setHelper(this) + binding.searchLayout.setHelper { showSearchBar() } binding.searchLayout.run { setPadding( paddingLeft, @@ -831,13 +830,11 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H STATE_SEARCH -> { mViewTransition!!.showView(1, animation) - binding.searchLayout.scrollSearchContainerToTop() selectSearchFab(animation) } STATE_SEARCH_SHOW_LIST -> { mViewTransition!!.showView(1, animation) - binding.searchLayout.scrollSearchContainerToTop() selectSearchFab(animation) } } @@ -847,15 +844,9 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H selectActionFab(animation) } - STATE_SEARCH -> { - mViewTransition!!.showView(1, animation) - binding.searchLayout.scrollSearchContainerToTop() - } + STATE_SEARCH -> mViewTransition!!.showView(1, animation) - STATE_SEARCH_SHOW_LIST -> { - mViewTransition!!.showView(1, animation) - binding.searchLayout.scrollSearchContainerToTop() - } + STATE_SEARCH_SHOW_LIST -> mViewTransition!!.showView(1, animation) } STATE_SEARCH, STATE_SEARCH_SHOW_LIST -> if (state == STATE_NORMAL) { @@ -926,10 +917,6 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H } } - override fun onChangeSearchMode() { - showSearchBar() - } - private fun onGetGalleryListSuccess(result: GalleryListParser.Result, taskId: Int) { if (Settings.preloadThumbAggressively) { lifecycleScope.launchIO { diff --git a/app/src/main/res/layout/filter_chip.xml b/app/src/main/res/layout/filter_chip.xml deleted file mode 100644 index d22809b30..000000000 --- a/app/src/main/res/layout/filter_chip.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/search_advance.xml b/app/src/main/res/layout/search_advance.xml deleted file mode 100644 index fbb1ecd56..000000000 --- a/app/src/main/res/layout/search_advance.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/app/src/main/res/layout/search_category.xml b/app/src/main/res/layout/search_category.xml deleted file mode 100644 index e28fc741b..000000000 --- a/app/src/main/res/layout/search_category.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/search_image.xml b/app/src/main/res/layout/search_image.xml deleted file mode 100644 index 93632b63a..000000000 --- a/app/src/main/res/layout/search_image.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/app/src/main/res/layout/search_normal.xml b/app/src/main/res/layout/search_normal.xml deleted file mode 100644 index c25fa515f..000000000 --- a/app/src/main/res/layout/search_normal.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/widget_advance_search_table.xml b/app/src/main/res/layout/widget_advance_search_table.xml deleted file mode 100644 index 0aa0d3ca7..000000000 --- a/app/src/main/res/layout/widget_advance_search_table.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -