Initial compose ImageSearchLayout layout
This commit is contained in:
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>
|
||||
Reference in new issue
Block a user