Initial compose ImageSearchLayout layout

This commit is contained in:
tarsin committed 2023-05-13 19:39:59 +08:00
1 parent 37dcad7d6f
commit c1eddb5876
5 files changed
+88 -359

No files matched your search

@@ -1,238 +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.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.AbsSavedState;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
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.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.ViewUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public final class ImageSearchLayout extends LinearLayout implements View.OnClickListener {
private static final String TAG = ImageSearchLayout.class.getSimpleName();
private ImageView mPreview;
private View mSelectImage;
private CheckBox mSearchUSS;
private CheckBox mSearchOSC;
private Helper mHelper;
@Nullable
private String mImagePath;
public ImageSearchLayout(Context context) {
super(context);
init(context);
}
public ImageSearchLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ImageSearchLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@SuppressWarnings("deprecation")
public void init(Context context) {
setOrientation(VERTICAL);
setDividerDrawable(context.getResources().getDrawable(R.drawable.spacer_keyline));
setShowDividers(SHOW_DIVIDER_MIDDLE);
setClipChildren(false);
setClipToPadding(false);
LayoutInflater.from(context).inflate(R.layout.widget_image_search, this);
mPreview = (ImageView) ViewUtils.$$(this, R.id.preview);
mSelectImage = ViewUtils.$$(this, R.id.select_image);
mSearchUSS = (CheckBox) ViewUtils.$$(this, R.id.search_uss);
mSearchOSC = (CheckBox) ViewUtils.$$(this, R.id.search_osc);
mSelectImage.setOnClickListener(this);
}
public void setHelper(Helper helper) {
mHelper = helper;
}
@Override
public void onClick(View v) {
if (v == mSelectImage) {
if (null != mHelper) {
mHelper.onSelectImage();
}
}
}
public void setImageUri(@Nullable Uri imageUri) {
if (null == imageUri) {
return;
}
Context context = getContext();
UniFile file = UniFile.fromUri(context, imageUri);
if (null == file) {
return;
}
try {
var src = ImageDecoder.createSource(context.getContentResolver(), imageUri);
Bitmap bitmap = ImageDecoder.decodeBitmap(src, Image.getImageSearchDecoderSampleListener());
File temp = AppConfig.createTempFile();
if (null == temp) {
return;
}
// TODO ehentai image search is bad when I'm writing this line.
// Re-compress image will make image search failed.
OutputStream os = null;
try {
os = new FileOutputStream(temp);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
mImagePath = temp.getPath();
mPreview.setImageBitmap(bitmap);
((View) mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(os);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void setImagePath(@Nullable String imagePath) {
if (null == imagePath) {
return;
}
InputStream is = null;
try {
is = new FileInputStream(imagePath);
Bitmap bitmap = BitmapFactory.decodeStream(is);
if (null == bitmap) {
return;
}
mImagePath = imagePath;
mPreview.setImageBitmap(bitmap);
((View) mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
// Ignore
} finally {
IOUtils.closeQuietly(is);
}
}
public void formatListUrlBuilder(ListUrlBuilder builder) throws EhException {
if (null == mImagePath) {
throw new EhException(getContext().getString(R.string.select_image_first));
}
builder.setImagePath(mImagePath);
builder.setUseSimilarityScan(mSearchUSS.isChecked());
builder.setOnlySearchCovers(mSearchOSC.isChecked());
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.imagePath = mImagePath;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setImagePath(ss.imagePath);
}
public interface Helper {
void onSelectImage();
}
private static class SavedState extends AbsSavedState {
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
String imagePath;
/**
* Constructor called from {@link ImageSearchLayout#onSaveInstanceState()}
*/
protected SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
protected SavedState(Parcel source) {
super(source);
imagePath = source.readString();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(imagePath);
}
}
}
@@ -0,0 +1,88 @@
package com.hippo.ehviewer.widget
import android.content.Context
import android.util.AttributeSet
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageOnly
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.saveable.rememberSaveable
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.R
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.hippo.util.pickVisualMedia
import kotlinx.coroutines.launch
class ImageSearchLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
) : AbstractComposeView(context, attrs, defStyle) {
@Composable
override fun Content() {
Mdc3Theme {
val coroutineScope = rememberCoroutineScope()
var uss by rememberSaveable { mutableStateOf(false) }
var osc by rememberSaveable { mutableStateOf(false) }
var path by rememberSaveable { mutableStateOf("") }
fun selectImage() = coroutineScope.launch {
path = pickVisualMedia(ImageOnly).toString()
}
Column(modifier = Modifier.fillMaxWidth()) {
Card(modifier = Modifier.align(Alignment.CenterHorizontally)) {
val maxSize = dimensionResource(id = R.dimen.image_search_max_size)
if (path.isNotBlank()) {
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) {
}
}
@@ -30,12 +30,9 @@ import android.widget.CompoundButton
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageOnly
import androidx.annotation.IntDef
import androidx.core.content.edit
import androidx.core.view.forEach
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
@@ -51,11 +48,9 @@ import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.util.pickVisualMedia
import com.hippo.widget.RadioGridGroup
import com.hippo.yorozuya.NumberUtils
import com.hippo.yorozuya.ViewUtils
import kotlinx.coroutines.launch
@SuppressLint("InflateParams")
class SearchLayout @JvmOverloads constructor(
@@ -148,12 +143,6 @@ class SearchLayout @JvmOverloads constructor(
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table)
// Create image view
mImageView = mInflater.inflate(R.layout.search_image, null) as ImageSearchLayout
mImageView.setHelper {
findViewTreeLifecycleOwner()!!.lifecycleScope.launch {
val uri = pickVisualMedia(ImageOnly)
mImageView.setImageUri(uri)
}
}
// Create action view
mActionView = mInflater.inflate(R.layout.search_action, null)
mActionView.layoutParams = LayoutParams(
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="@dimen/keyline_margin"
android:height="@dimen/keyline_margin" />
<solid android:color="@android:color/transparent" />
</shape>
@@ -1,84 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone">
<com.hippo.widget.FixedAspectImageView
android:id="@+id/preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:maxWidth="@dimen/image_search_max_size"
android:maxHeight="@dimen/image_search_max_size" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/select_image"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/select_image" />
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TableRow>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/search_uss"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="true"
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/search_uss" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/search_osc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false"
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/search_osc" />
</TableRow>
<TableRow>
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false" />
</TableRow>
</TableLayout>
</merge>