Compare commits

..
15 Commits
Author SHA1 Message Date
tarsin ae941a4955 Bump Version
EhViewer / Build (push) Has been cancelled
2022-05-20 00:39:47 +08:00
tarsin 72da7ebfe4 Fix search crash 2022-05-20 00:39:07 +08:00
tarsin 8e1bacbfac Refactor Search 2022-05-19 23:59:18 +08:00
tarsin 747c19c028 Rename .java to .kt 2022-05-19 23:59:18 +08:00
txyyh aca83fc1ee Cut photo edges 2022-05-19 21:25:36 +08:00
tarsin 35babd089d Image search layout tweaks 2022-05-19 21:06:23 +08:00
tarsin 457ea56aab Reformat code 2022-05-19 20:18:59 +08:00
txyyh ed22de2360 Cut photo background 2022-05-19 20:17:13 +08:00
tarsin 8d386b44bc Kill redundant things in navmain 2022-05-19 20:16:35 +08:00
tarsin c4c88c40c5 Surround Image with round corner cardview 2022-05-19 20:07:37 +08:00
tarsin e637ad6acb Honor uploader with Chip 2022-05-19 19:48:27 +08:00
tarsin 3b9e08e1c7 Don't disturb nekochan 2022-05-19 18:30:34 +08:00
tarsin 4a2aa8a975 Update README.md 2022-05-19 14:14:42 +08:00
txyyh 9c08eda38a Rewrite README commit
Rewrite commit 713d972d16 to make it look normal
2022-05-19 13:18:01 +08:00
tarsin 713d972d16 Update README.md 2022-05-19 00:46:49 +08:00
28 changed files with 554 additions and 1182 deletions

No files matched your search

+10 -1
View File
@@ -2,10 +2,19 @@
![Icon](art/launcher_icon-web.png)
这是一个 E-Hentai Android 平台的浏览器。
这是一个 E-Hentai Android 平台的浏览器
已针对性适配 Material Design 3 及其动态取色系统
仅供 Android 12 及以上平台使用!
An E-Hentai Application for Android.
with Material Design 3 and Dynamic Color Support.
Only Support Android S and above!
# Download
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
+2 -2
View File
@@ -36,8 +36,8 @@ android {
applicationId "io.github.nekoinverter.ehviewer"
minSdkVersion 31
targetSdkVersion 32
versionCode 114517
versionName "1.7.26.8"
versionCode 114518
versionName "1.7.26.9"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -297,11 +297,6 @@ public class EhApplication extends SceneApplication {
return application.mFavouriteStatusRouter;
}
@NonNull
public static String getDeveloperEmail() {
return "nekoworkshop$protonmail.com".replace('$', '@');
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowInsets;
@@ -221,16 +221,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private int mLayoutMode;
private int mSize;
private int mCurrentIndex;
private int mSavingPage = -1; ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
private int mSavingPage = -1;
private void buildProvider() {
if (mGalleryProvider != null) {
@@ -259,7 +250,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
mGalleryProvider = new ArchiveGalleryProvider(this, mUri);
}
}
}
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
private void onInit() {
Intent intent = getIntent();
@@ -1304,4 +1304,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.Uri;
@@ -33,7 +32,6 @@ import android.text.TextUtils;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
@@ -43,7 +41,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -85,7 +82,6 @@ import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
import com.hippo.widget.DrawerView;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
@@ -96,8 +92,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import rikka.material.app.DayNightDelegate;
public final class MainActivity extends StageActivity
implements NavigationView.OnNavigationItemSelectedListener {
@@ -139,10 +133,6 @@ public final class MainActivity extends StageActivity
private NavigationView mNavView;
@Nullable
private DrawerView mRightDrawer;
@Nullable
private LoadImageView mAvatar;
@Nullable
private TextView mDisplayName;
private int mNavCheckedItem = 0;
@Override
@@ -320,19 +310,6 @@ public final class MainActivity extends StageActivity
mDrawerLayout.setStatusBarBackgroundColor(0);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
View headerLayout = mNavView.getHeaderView(0);
mAvatar = (LoadImageView) ViewUtils.$$(headerLayout, R.id.avatar);
mDisplayName = (TextView) ViewUtils.$$(headerLayout, R.id.display_name);
ViewUtils.$$(headerLayout, R.id.night_mode).setOnClickListener(v -> {
int theme = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0 ? DayNightDelegate.MODE_NIGHT_NO : DayNightDelegate.MODE_NIGHT_YES;
DayNightDelegate.setDefaultNightMode(theme);
recreate();
if (Settings.getTheme() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
Settings.putTheme(theme);
}
});
updateProfile();
if (mNavView != null) {
mNavView.setNavigationItemSelectedListener(this);
@@ -440,23 +417,18 @@ public final class MainActivity extends StageActivity
mDrawerLayout = null;
mNavView = null;
mRightDrawer = null;
mAvatar = null;
mDisplayName = null;
}
@Override
protected void onResume() {
super.onResume();
setNavCheckedItem(mNavCheckedItem);
checkClipboardUrl();
}
@Override
protected void onTransactScene() {
super.onTransactScene();
checkClipboardUrl();
}
@@ -546,25 +518,6 @@ public final class MainActivity extends StageActivity
}
}
public void updateProfile() {
if (null == mAvatar || null == mDisplayName) {
return;
}
String avatarUrl = Settings.getAvatar();
if (TextUtils.isEmpty(avatarUrl)) {
mAvatar.load(R.drawable.default_avatar);
} else {
mAvatar.load(avatarUrl, avatarUrl);
}
String displayName = Settings.getDisplayName();
if (TextUtils.isEmpty(displayName)) {
displayName = getString(R.string.default_display_name);
}
mDisplayName.setText(displayName);
}
public void addAboveSnackView(View view) {
if (mStageLayout != null) {
mStageLayout.addAboveSnackView(view);
@@ -21,40 +21,19 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
import com.hippo.util.AppHelper;
public class AboutFragment extends BasePreferenceFragment {
private static final String KEY_AUTHOR = "author";
//private static final String KEY_DONATE = "donate";
private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates";
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.about_settings);
Preference author = findPreference(KEY_AUTHOR);
//Preference donate = findPreference(KEY_DONATE);
Preference checkForUpdate = findPreference(KEY_CHECK_FOR_UPDATES);
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@')));
author.setOnPreferenceClickListener(this);
//donate.setOnPreferenceClickListener(this);
checkForUpdate.setOnPreferenceClickListener(this);
}
@Override
public boolean onPreferenceClick(Preference preference) {
String key = preference.getKey();
if (KEY_AUTHOR.equals(key)) {
AppHelper.sendEmail(requireActivity(), EhApplication.getDeveloperEmail(),
"About EhViewer", null);
}
return true;
}
@Override
@@ -58,13 +58,6 @@ public abstract class BaseScene extends SceneFragment {
private SparseArray<Parcelable> drawerViewState;
private boolean needWhiteStatusBar = needWhiteStatusBar();
public void updateAvatar() {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).updateProfile();
}
}
public void addAboveSnackView(View view) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
@@ -357,7 +357,6 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
public void onGetProfileEnd() {
mSigningIn = false;
updateAvatar();
redirectTo();
}
@@ -1,163 +0,0 @@
/*
* Copyright (C) 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.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.widget.CheckTextView;
import com.hippo.yorozuya.NumberUtils;
public class CategoryTable extends TableLayout implements View.OnLongClickListener {
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_CATEGORY = "category";
private CheckTextView mDoujinshi;
private CheckTextView mManga;
private CheckTextView mArtistCG;
private CheckTextView mGameCG;
private CheckTextView mWestern;
private CheckTextView mNonH;
private CheckTextView mImageSets;
private CheckTextView mCosplay;
private CheckTextView mAsianPorn;
private CheckTextView mMisc;
private CheckTextView[] mOptions;
public CategoryTable(Context context) {
super(context);
init();
}
public CategoryTable(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void init() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_category_table, this);
ViewGroup row0 = (ViewGroup) getChildAt(0);
mDoujinshi = (CheckTextView) row0.getChildAt(0);
mManga = (CheckTextView) row0.getChildAt(1);
ViewGroup row1 = (ViewGroup) getChildAt(1);
mArtistCG = (CheckTextView) row1.getChildAt(0);
mGameCG = (CheckTextView) row1.getChildAt(1);
ViewGroup row2 = (ViewGroup) getChildAt(2);
mWestern = (CheckTextView) row2.getChildAt(0);
mNonH = (CheckTextView) row2.getChildAt(1);
ViewGroup row3 = (ViewGroup) getChildAt(3);
mImageSets = (CheckTextView) row3.getChildAt(0);
mCosplay = (CheckTextView) row3.getChildAt(1);
ViewGroup row4 = (ViewGroup) getChildAt(4);
mAsianPorn = (CheckTextView) row4.getChildAt(0);
mMisc = (CheckTextView) row4.getChildAt(1);
mOptions = new CheckTextView[]{
mDoujinshi, mManga, mArtistCG, mGameCG, mWestern,
mNonH, mImageSets, mCosplay, mAsianPorn, mMisc
};
for (CheckTextView option : mOptions) {
option.setOnLongClickListener(this);
}
}
@Override
public boolean onLongClick(View v) {
if (v instanceof CheckTextView) {
boolean checked = ((CheckTextView) v).isChecked();
for (CheckTextView option : mOptions) {
if (option != v) {
option.setChecked(!checked);
}
}
}
return true;
}
/**
* Get category according to button.
*
* @return the category of this view
*/
public int getCategory() {
int category = 0;
if (!mDoujinshi.isChecked()) category |= EhConfig.DOUJINSHI;
if (!mManga.isChecked()) category |= EhConfig.MANGA;
if (!mArtistCG.isChecked()) category |= EhConfig.ARTIST_CG;
if (!mGameCG.isChecked()) category |= EhConfig.GAME_CG;
if (!mWestern.isChecked()) category |= EhConfig.WESTERN;
if (!mNonH.isChecked()) category |= EhConfig.NON_H;
if (!mImageSets.isChecked()) category |= EhConfig.IMAGE_SET;
if (!mCosplay.isChecked()) category |= EhConfig.COSPLAY;
if (!mAsianPorn.isChecked()) category |= EhConfig.ASIAN_PORN;
if (!mMisc.isChecked()) category |= EhConfig.MISC;
return category;
}
/**
* Set each button checked or not according to category.
*
* @param category target category
*/
public void setCategory(int category) {
mDoujinshi.setChecked(!NumberUtils.int2boolean(category & EhConfig.DOUJINSHI));
mManga.setChecked(!NumberUtils.int2boolean(category & EhConfig.MANGA));
mArtistCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.ARTIST_CG));
mGameCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.GAME_CG));
mWestern.setChecked(!NumberUtils.int2boolean(category & EhConfig.WESTERN));
mNonH.setChecked(!NumberUtils.int2boolean(category & EhConfig.NON_H));
mImageSets.setChecked(!NumberUtils.int2boolean(category & EhConfig.IMAGE_SET));
mCosplay.setChecked(!NumberUtils.int2boolean(category & EhConfig.COSPLAY));
mAsianPorn.setChecked(!NumberUtils.int2boolean(category & EhConfig.ASIAN_PORN));
mMisc.setChecked(!NumberUtils.int2boolean(category & EhConfig.MISC));
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_CATEGORY, getCategory());
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
setCategory(savedState.getInt(STATE_KEY_CATEGORY));
}
}
}
@@ -141,7 +141,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
mImagePath = temp.getPath();
mPreview.setImageBitmap(bitmap);
mPreview.setVisibility(VISIBLE);
((View)mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
@@ -166,7 +166,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
}
mImagePath = imagePath;
mPreview.setImageBitmap(bitmap);
mPreview.setVisibility(VISIBLE);
((View)mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
// Ignore
} finally {
@@ -1,403 +0,0 @@
/*
* Copyright (C) 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.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.tabs.TabLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.easyrecyclerview.SimpleHolder;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.widget.RadioGridGroup;
import com.hippo.yorozuya.ViewUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnCheckedChangeListener,
View.OnClickListener, ImageSearchLayout.Helper, TabLayout.OnTabSelectedListener {
public static final int SEARCH_MODE_NORMAL = 0;
public static final int SEARCH_MODE_IMAGE = 1;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_SEARCH_MODE = "search_mode";
private static final String STATE_KEY_ENABLE_ADVANCE = "enable_advance";
private static final int ITEM_TYPE_NORMAL = 0;
private static final int ITEM_TYPE_NORMAL_ADVANCE = 1;
private static final int ITEM_TYPE_IMAGE = 2;
private static final int ITEM_TYPE_ACTION = 3;
private static final int[] SEARCH_ITEM_COUNT_ARRAY = {
3, 2
};
private static final int[][] SEARCH_ITEM_TYPE = {
{ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION}, // SEARCH_MODE_NORMAL
{ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION}, // SEARCH_MODE_IMAGE
};
private LayoutInflater mInflater;
@SearchMode
private int mSearchMode = SEARCH_MODE_NORMAL;
private boolean mEnableAdvance = false;
private View mNormalView;
private CategoryTable mCategoryTable;
private RadioGridGroup mNormalSearchMode;
private ImageView mNormalSearchModeHelp;
private Switch mEnableAdvanceSwitch;
private View mAdvanceView;
private AdvanceSearchTable mTableAdvanceSearch;
private ImageSearchLayout mImageView;
private View mActionView;
private TabLayout mAction;
private LinearLayoutManager mLayoutManager;
private SearchAdapter mAdapter;
private Helper mHelper;
public SearchLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SearchLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@SuppressLint("InflateParams")
private void init(Context context) {
Resources resources = context.getResources();
mInflater = LayoutInflater.from(context);
mLayoutManager = new SearchLayoutManager(context);
mAdapter = new SearchAdapter();
mAdapter.setHasStableIds(true);
setLayoutManager(mLayoutManager);
setAdapter(mAdapter);
setHasFixedSize(true);
setClipToPadding(false);
((DefaultItemAnimator) getItemAnimator()).setSupportsChangeAnimations(false);
int interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval);
int paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h);
int paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v);
MarginItemDecoration decoration = new MarginItemDecoration(
interval, paddingH, paddingV, paddingH, paddingV);
addItemDecoration(decoration);
decoration.applyPaddings(this);
// Create normal view
View normalView = mInflater.inflate(R.layout.search_normal, null);
mNormalView = normalView;
mCategoryTable = normalView.findViewById(R.id.search_category_table);
mNormalSearchMode = normalView.findViewById(R.id.normal_search_mode);
mNormalSearchModeHelp = normalView.findViewById(R.id.normal_search_mode_help);
mEnableAdvanceSwitch = normalView.findViewById(R.id.search_enable_advance);
mNormalSearchModeHelp.setOnClickListener(this);
mEnableAdvanceSwitch.setOnCheckedChangeListener(this);
mEnableAdvanceSwitch.setSwitchPadding(resources.getDimensionPixelSize(R.dimen.switch_padding));
// Create advance view
mAdvanceView = mInflater.inflate(R.layout.search_advance, null);
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table);
// Create image view
mImageView = (ImageSearchLayout) mInflater.inflate(R.layout.search_image, null);
mImageView.setHelper(this);
// Create action view
mActionView = mInflater.inflate(R.layout.search_action, null);
mActionView.setLayoutParams(new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mAction = mActionView.findViewById(R.id.action);
mAction.addOnTabSelectedListener(this);
}
public void setHelper(Helper helper) {
mHelper = helper;
}
public void scrollSearchContainerToTop() {
mLayoutManager.scrollToPositionWithOffset(0, 0);
}
public void setImageUri(Uri imageUri) {
mImageView.setImageUri(imageUri);
}
public void setNormalSearchMode(int id) {
mNormalSearchMode.check(id);
}
@Override
public void onSelectImage() {
if (mHelper != null) {
mHelper.onSelectImage();
}
}
@Override
protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
mNormalView.saveHierarchyState(container);
mAdvanceView.saveHierarchyState(container);
mImageView.saveHierarchyState(container);
mActionView.saveHierarchyState(container);
}
@Override
protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
mNormalView.restoreHierarchyState(container);
mAdvanceView.restoreHierarchyState(container);
mImageView.restoreHierarchyState(container);
mActionView.restoreHierarchyState(container);
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_SEARCH_MODE, mSearchMode);
state.putBoolean(STATE_KEY_ENABLE_ADVANCE, mEnableAdvance);
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
mSearchMode = savedState.getInt(STATE_KEY_SEARCH_MODE);
mEnableAdvance = savedState.getBoolean(STATE_KEY_ENABLE_ADVANCE);
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView == mEnableAdvanceSwitch) {
post(() -> {
mEnableAdvance = isChecked;
if (mSearchMode == SEARCH_MODE_NORMAL) {
if (mEnableAdvance) {
mAdapter.notifyItemInserted(1);
} else {
mAdapter.notifyItemRemoved(1);
}
if (mHelper != null) {
mHelper.onChangeSearchMode();
}
}
});
}
}
@SuppressLint("NonConstantResourceId")
public void formatListUrlBuilder(ListUrlBuilder urlBuilder, String query) throws EhException {
urlBuilder.reset();
switch (mSearchMode) {
case SEARCH_MODE_NORMAL:
int nsMode = mNormalSearchMode.getCheckedRadioButtonId();
if (nsMode == R.id.search_subscription_search) {
urlBuilder.setMode(ListUrlBuilder.MODE_SUBSCRIPTION);
} else if (nsMode == R.id.search_specify_uploader) {
urlBuilder.setMode(ListUrlBuilder.MODE_UPLOADER);
} else if (nsMode == R.id.search_specify_tag) {
urlBuilder.setMode(ListUrlBuilder.MODE_TAG);
} else {
urlBuilder.setMode(ListUrlBuilder.MODE_NORMAL);
}
urlBuilder.setKeyword(query);
urlBuilder.setCategory(mCategoryTable.getCategory());
if (mEnableAdvance) {
urlBuilder.setAdvanceSearch(mTableAdvanceSearch.getAdvanceSearch());
urlBuilder.setMinRating(mTableAdvanceSearch.getMinRating());
urlBuilder.setPageFrom(mTableAdvanceSearch.getPageFrom());
urlBuilder.setPageTo(mTableAdvanceSearch.getPageTo());
}
break;
case SEARCH_MODE_IMAGE:
urlBuilder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
mImageView.formatListUrlBuilder(urlBuilder);
break;
}
}
@Override
public void onClick(View v) {
if (mNormalSearchModeHelp == v) {
new MaterialAlertDialogBuilder(getContext())
.setMessage(R.string.search_tip)
.show();
}
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
post(() -> setSearchMode(tab.getPosition()));
}
public void setSearchMode(@SearchMode int mode) {
int oldItemCount = mAdapter.getItemCount();
mSearchMode = mode;
int newItemCount = mAdapter.getItemCount();
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1);
mAdapter.notifyItemRangeInserted(0, newItemCount - 1);
if (mHelper != null) {
mHelper.onChangeSearchMode();
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
@IntDef({SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE})
@Retention(RetentionPolicy.SOURCE)
private @interface SearchMode {
}
public interface Helper {
void onChangeSearchMode();
void onSelectImage();
}
static class SearchLayoutManager extends LinearLayoutManager {
public SearchLayoutManager(Context context) {
super(context);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
private class SearchAdapter extends EasyRecyclerView.Adapter<SimpleHolder> {
@Override
public int getItemCount() {
int count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode];
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
count--;
}
return count;
}
@Override
public int getItemViewType(int position) {
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION;
}
return type;
}
@NonNull
@Override
public SimpleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
if (viewType == ITEM_TYPE_ACTION) {
ViewUtils.removeFromParent(mActionView);
view = mActionView;
} else {
view = mInflater.inflate(R.layout.search_category, parent, false);
TextView title = view.findViewById(R.id.category_title);
FrameLayout content = view.findViewById(R.id.category_content);
switch (viewType) {
case ITEM_TYPE_NORMAL:
title.setText(R.string.search_normal);
ViewUtils.removeFromParent(mNormalView);
content.addView(mNormalView);
break;
case ITEM_TYPE_NORMAL_ADVANCE:
title.setText(R.string.search_advance);
ViewUtils.removeFromParent(mAdvanceView);
content.addView(mAdvanceView);
break;
case ITEM_TYPE_IMAGE:
title.setText(R.string.search_image);
ViewUtils.removeFromParent(mImageView);
content.addView(mImageView);
break;
}
}
return new SimpleHolder(view);
}
@Override
public void onBindViewHolder(SimpleHolder holder, int position) {
if (holder.getItemViewType() == ITEM_TYPE_ACTION) {
mAction.selectTab(mAction.getTabAt(mSearchMode));
}
}
@Override
public long getItemId(int position) {
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION;
}
return type;
}
}
}
@@ -0,0 +1,412 @@
/*
* Copyright (C) 2022 tarsin norbin
*
* 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 com.hippo.easyrecyclerview.EasyRecyclerView
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import android.view.LayoutInflater
import com.google.android.material.chip.ChipGroup
import com.hippo.widget.RadioGridGroup
import com.google.android.material.tabs.TabLayout
import androidx.recyclerview.widget.LinearLayoutManager
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import androidx.recyclerview.widget.DefaultItemAnimator
import com.hippo.ehviewer.R
import com.hippo.easyrecyclerview.MarginItemDecoration
import android.view.ViewGroup
import android.util.SparseArray
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import android.widget.*
import kotlin.Throws
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.annotation.IntDef
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.android.material.chip.Chip
import com.hippo.easyrecyclerview.SimpleHolder
import com.hippo.ehviewer.client.EhConfig
import com.hippo.yorozuya.NumberUtils
import com.hippo.yorozuya.ViewUtils
import java.lang.IndexOutOfBoundsException
class SearchLayout : EasyRecyclerView, CompoundButton.OnCheckedChangeListener, View.OnClickListener,
ImageSearchLayout.Helper, OnTabSelectedListener {
private val mInflater : LayoutInflater
private val mSharePref : SharedPreferences
@SearchMode
private var mSearchMode = SEARCH_MODE_NORMAL
private var mEnableAdvance = false
private var mNormalView: View? = null
private var mNormalSearchMode: RadioGridGroup? = null
private var mNormalSearchModeHelp: ImageView? = null
private var mEnableAdvanceSwitch: Switch? = null
private var mAdvanceView: View? = null
private var mTableAdvanceSearch: AdvanceSearchTable? = null
private var mImageView: ImageSearchLayout? = null
private var mActionView: View? = null
private var mAction: TabLayout? = null
private var mLayoutManager: LinearLayoutManager? = null
private var mAdapter: SearchAdapter? = null
private var mHelper: Helper? = null
private val mCategoryTable : ArrayList<Pair<Int, Int>> = arrayListOf(
Pair(EhConfig.DOUJINSHI, R.string.doujinshi),
Pair(EhConfig.MANGA, R.string.manga),
Pair(EhConfig.ARTIST_CG, R.string.artist_cg),
Pair(EhConfig.GAME_CG, R.string.game_cg),
Pair(EhConfig.WESTERN, R.string.western),
Pair(EhConfig.NON_H, R.string.non_h),
Pair(EhConfig.IMAGE_SET, R.string.image_set),
Pair(EhConfig.COSPLAY, R.string.cosplay),
Pair(EhConfig.ASIAN_PORN, R.string.asian_porn),
Pair(EhConfig.MISC, R.string.misc)
)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
mSharePref = PreferenceManager.getDefaultSharedPreferences(context)
mInflater = LayoutInflater.from(context)
init(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
context,
attrs,
defStyle
) {
mSharePref = PreferenceManager.getDefaultSharedPreferences(context)
mInflater = LayoutInflater.from(context)
init(context)
}
@SuppressLint("InflateParams")
private fun init(context: Context) {
val resources = context.resources
mLayoutManager = SearchLayoutManager(context)
mAdapter = SearchAdapter()
mAdapter!!.setHasStableIds(true)
layoutManager = mLayoutManager
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
val normalView = mInflater.inflate(R.layout.search_normal, null)
mNormalView = normalView
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
val mCategoryGroup = normalView.findViewById(R.id.search_category_chipgroup) as ChipGroup
for (mPair in mCategoryTable) {
val mChip = IdentifiedChip(context)
mChip.isCheckable = true
mChip.setText(mPair.second)
mChip.idt = mPair.first
mChip.isChecked = NumberUtils.int2boolean(mPair.first and mCategoryStored)
mCategoryGroup.addView(mChip)
}
mCategoryGroup.setOnCheckedStateChangeListener { _, checkedIds ->
var mCategory = 0
for (index in checkedIds) {
mCategory = mCategory or findViewById<IdentifiedChip>(index).idt
}
mSharePref.edit { putInt(SEARCH_CATEGORY_PREF, mCategory) }
}
mNormalSearchMode = normalView.findViewById(R.id.normal_search_mode)
mNormalSearchModeHelp = normalView.findViewById(R.id.normal_search_mode_help)
mEnableAdvanceSwitch = normalView.findViewById(R.id.search_enable_advance)
mNormalSearchModeHelp!!.setOnClickListener(this)
mEnableAdvanceSwitch!!.setOnCheckedChangeListener(this)
mEnableAdvanceSwitch!!.switchPadding = resources.getDimensionPixelSize(R.dimen.switch_padding)
// Create advance view
mAdvanceView = mInflater.inflate(R.layout.search_advance, null)
mTableAdvanceSearch = mAdvanceView!!.findViewById(R.id.search_advance_search_table)
// Create image view
mImageView = mInflater.inflate(R.layout.search_image, null) as ImageSearchLayout
mImageView!!.setHelper(this)
// 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() {
mLayoutManager!!.scrollToPositionWithOffset(0, 0)
}
fun setImageUri(imageUri: Uri?) {
mImageView!!.setImageUri(imageUri)
}
fun setNormalSearchMode(id: Int) {
mNormalSearchMode!!.check(id)
}
override fun onSelectImage() {
if (mHelper != null) {
mHelper!!.onSelectImage()
}
}
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
super.dispatchSaveInstanceState(container)
mNormalView!!.saveHierarchyState(container)
mAdvanceView!!.saveHierarchyState(container)
mImageView!!.saveHierarchyState(container)
mActionView!!.saveHierarchyState(container)
}
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
super.dispatchRestoreInstanceState(container)
mNormalView!!.restoreHierarchyState(container)
mAdvanceView!!.restoreHierarchyState(container)
mImageView!!.restoreHierarchyState(container)
mActionView!!.restoreHierarchyState(container)
}
override fun onSaveInstanceState(): Parcelable {
val state = Bundle()
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState())
state.putInt(STATE_KEY_SEARCH_MODE, mSearchMode)
state.putBoolean(STATE_KEY_ENABLE_ADVANCE, mEnableAdvance)
return state
}
override fun onRestoreInstanceState(state: Parcelable) {
if (state is Bundle) {
super.onRestoreInstanceState(state.getParcelable(STATE_KEY_SUPER))
mSearchMode = state.getInt(STATE_KEY_SEARCH_MODE)
mEnableAdvance = state.getBoolean(STATE_KEY_ENABLE_ADVANCE)
}
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
if (buttonView === mEnableAdvanceSwitch) {
post {
mEnableAdvance = isChecked
if (mSearchMode == SEARCH_MODE_NORMAL) {
if (mEnableAdvance) {
mAdapter!!.notifyItemInserted(1)
} else {
mAdapter!!.notifyItemRemoved(1)
}
if (mHelper != null) {
mHelper!!.onChangeSearchMode()
}
}
}
}
}
@SuppressLint("NonConstantResourceId")
@Throws(EhException::class)
fun formatListUrlBuilder(urlBuilder: ListUrlBuilder, query: String?) {
urlBuilder.reset()
when (mSearchMode) {
SEARCH_MODE_NORMAL -> {
when (mNormalSearchMode!!.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
}
}
urlBuilder.keyword = query
urlBuilder.category = PreferenceManager.getDefaultSharedPreferences(context).getInt(
SEARCH_CATEGORY_PREF, 0)
if (mEnableAdvance) {
urlBuilder.advanceSearch = mTableAdvanceSearch!!.advanceSearch
urlBuilder.minRating = mTableAdvanceSearch!!.minRating
urlBuilder.pageFrom = mTableAdvanceSearch!!.pageFrom
urlBuilder.pageTo = mTableAdvanceSearch!!.pageTo
}
}
SEARCH_MODE_IMAGE -> {
urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
mImageView!!.formatListUrlBuilder(urlBuilder)
}
}
}
override fun onClick(v: View) {
if (mNormalSearchModeHelp === v) {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.search_tip)
.show()
}
}
override fun onTabSelected(tab: TabLayout.Tab) {
post { setSearchMode(tab.position) }
}
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)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
private annotation class SearchMode
interface Helper {
fun onChangeSearchMode()
fun onSelectImage()
}
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 SearchAdapter : Adapter<SimpleHolder>() {
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) {
ViewUtils.removeFromParent(mActionView)
view = mActionView
} else {
view = mInflater.inflate(R.layout.search_category, parent, false)
val title = view.findViewById<TextView>(R.id.category_title)
val content = view.findViewById<FrameLayout>(R.id.category_content)
when (viewType) {
ITEM_TYPE_NORMAL -> {
title.setText(R.string.search_normal)
ViewUtils.removeFromParent(mNormalView)
content.addView(mNormalView)
}
ITEM_TYPE_NORMAL_ADVANCE -> {
title.setText(R.string.search_advance)
ViewUtils.removeFromParent(mAdvanceView)
content.addView(mAdvanceView)
}
ITEM_TYPE_IMAGE -> {
title.setText(R.string.search_image)
ViewUtils.removeFromParent(mImageView)
content.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()
}
}
inner class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(context, attrs) {
var idt = 0
}
companion object {
const val SEARCH_MODE_NORMAL = 0
const val SEARCH_MODE_IMAGE = 1
private const val STATE_KEY_SUPER = "super"
private const val STATE_KEY_SEARCH_MODE = "search_mode"
private const val STATE_KEY_ENABLE_ADVANCE = "enable_advance"
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 SEARCH_ITEM_TYPE = arrayOf(
intArrayOf(ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION), intArrayOf(
ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION
)
)
}
}
@@ -41,7 +41,8 @@ public class BatteryView extends AppCompatTextView {
private boolean mCharging = false;
private BatteryDrawable mDrawable;
private boolean mAttached = false; private final Runnable mCharger = new Runnable() {
private boolean mAttached = false;
private boolean mIsChargerWorking = false; private final Runnable mCharger = new Runnable() {
private int level = 0;
@@ -55,13 +56,35 @@ public class BatteryView extends AppCompatTextView {
getHandler().postDelayed(mCharger, 200);
}
};
private boolean mIsChargerWorking = false;
public BatteryView(Context context) {
super(context);
init();
}
public BatteryView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
TypedArray typedArray = context.obtainStyledAttributes(
attrs, R.styleable.BatteryView, defStyleAttr, 0);
mColor = typedArray.getColor(R.styleable.BatteryView_color, Color.WHITE);
mWarningColor = typedArray.getColor(R.styleable.BatteryView_warningColor, Color.RED);
typedArray.recycle();
mDrawable.setColor(mColor);
mDrawable.setWarningColor(mWarningColor);
}
private void init() {
mDrawable = new BatteryDrawable();
int height = (int) getTextSize();
mDrawable.setBounds(0, 0, (int) (height / 0.618f), height);
setCompoundDrawables(mDrawable, null, null, null);
} private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -93,28 +116,6 @@ public class BatteryView extends AppCompatTextView {
}
};
public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
TypedArray typedArray = context.obtainStyledAttributes(
attrs, R.styleable.BatteryView, defStyleAttr, 0);
mColor = typedArray.getColor(R.styleable.BatteryView_color, Color.WHITE);
mWarningColor = typedArray.getColor(R.styleable.BatteryView_warningColor, Color.RED);
typedArray.recycle();
mDrawable.setColor(mColor);
mDrawable.setWarningColor(mWarningColor);
}
private void init() {
mDrawable = new BatteryDrawable();
int height = (int) getTextSize();
mDrawable.setBounds(0, 0, (int) (height / 0.618f), height);
setCompoundDrawables(mDrawable, null, null, null);
}
@Override
public void setTextColor(int color) {
if (mCurrentColor == color) {
@@ -173,4 +174,6 @@ public class BatteryView extends AppCompatTextView {
}
@@ -1,236 +0,0 @@
/*
* Copyright (C) 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.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatCheckedTextView;
import com.hippo.ehviewer.R;
public class CheckTextView extends AppCompatCheckedTextView implements View.OnClickListener {
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
private Drawable mForeground;
private int mForegroundGravity = Gravity.FILL;
public CheckTextView(Context context) {
this(context, null);
}
public CheckTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckTextView,
defStyleAttr, 0);
mForegroundGravity = a.getInt(
R.styleable.CheckTextView_android_foregroundGravity, mForegroundGravity);
Drawable d = a.getDrawable(R.styleable.CheckTextView_android_foreground);
if (d != null) {
setForeground(d);
}
mForegroundInPadding = a.getBoolean(
R.styleable.CheckTextView_foregroundInsidePadding, true);
a.recycle();
setOnClickListener(this);
}
/**
* Describes how the foreground is positioned.
*
* @return foreground gravity.
* @see #setForegroundGravity(int)
*/
public int getForegroundGravity() {
return mForegroundGravity;
}
/**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link android.view.Gravity}
* @see #getForegroundGravity()
*/
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.TOP;
}
mForegroundGravity = foregroundGravity;
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
Rect padding = new Rect();
mForeground.getPadding(padding);
}
requestLayout();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) {
mForeground.jumpToCurrentState();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* Returns the drawable used as the foreground of this FrameLayout. The
* foreground drawable, if non-null, is always drawn on top of the children.
*
* @return A Drawable or null if no foreground was set.
*/
public Drawable getForeground() {
return mForeground;
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout. Any padding in the Drawable will be taken
* into account by ensuring that the children are inset to be placed
* inside of the padding area.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (mForeground != drawable) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = drawable;
if (drawable != null) {
setWillNotDraw(false);
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
if (mForegroundGravity == Gravity.FILL) {
Rect padding = new Rect();
drawable.getPadding(padding);
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mForegroundBoundsChanged |= changed;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mForegroundBoundsChanged = true;
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = getRight() - getLeft();
final int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
w - getPaddingRight(), h - getPaddingBottom());
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mForeground != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mForeground.setHotspot(x, y);
}
}
}
@Override
public void onClick(View v) {
setChecked(!isChecked());
}
}
@@ -55,7 +55,17 @@ public class TextClock extends AppCompatTextView {
private boolean mAttached;
private Calendar mTime;
private String mTimeZone; private final Runnable mTicker = new Runnable() {
private String mTimeZone;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
}; private final Runnable mTicker = new Runnable() {
@Override
public void run() {
onTimeChanged();
@@ -66,16 +76,12 @@ public class TextClock extends AppCompatTextView {
getHandler().postAtTime(mTicker, next);
}
};
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
}; private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
public TextClock(Context context) {
this(context, null);
}
public TextClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
chooseFormat();
@@ -88,12 +94,6 @@ public class TextClock extends AppCompatTextView {
onTimeChanged();
}
};
public TextClock(Context context) {
this(context, null);
}
public TextClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextClock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
@@ -261,4 +261,6 @@ public class TextClock extends AppCompatTextView {
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

@@ -65,38 +65,30 @@
android:layout_alignTop="@id/thumb"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:layout_toRightOf="@id/thumb"
android:layout_toEndOf="@id/thumb"
android:ellipsize="end"
android:maxLines="5"
android:textSize="@dimen/text_little_large" />
<TextView
<com.google.android.material.chip.Chip
android:id="@+id/uploader"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_marginTop="8dp"
android:layout_marginRight="@dimen/keyline_margin"
android:background="?selectableItemBackground"
android:layout_alignStart="@id/title"
android:ellipsize="end"
android:singleLine="true"
android:textSize="@dimen/text_little_small" />
android:textAllCaps="true" />
<TextView
<com.google.android.material.chip.Chip
android:id="@+id/category"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/uploader"
android:layout_alignLeft="@id/title"
android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/keyline_margin"
android:background="@drawable/category_background"
android:layout_alignStart="@id/title"
android:ellipsize="end"
android:paddingLeft="8dp"
android:paddingTop="2dp"
android:paddingRight="8dp"
android:paddingBottom="2dp"
android:singleLine="true"
android:textAllCaps="true"
android:textStyle="bold" />
+24 -16
View File
@@ -29,13 +29,21 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height"
app:maxAspect="1.333"
app:minAspect="0.333"
app:retryType="click" />
android:layout_height="@dimen/gallery_list_thumb_height">
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:maxAspect="1.333"
app:minAspect="0.333"
app:retryType="click" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/title"
@@ -46,7 +54,7 @@
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:layout_toRightOf="@id/thumb" />
android:layout_toEndOf="@id/thumb_card" />
<TextView
android:id="@+id/uploader"
@@ -54,7 +62,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginTop="2dp" />
<TextView
@@ -62,8 +70,8 @@
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignBottom="@id/thumb"
android:layout_alignStart="@id/title"
android:layout_alignBottom="@id/thumb_card"
android:layout_marginBottom="8dp"
android:paddingLeft="8dp"
android:paddingTop="2dp"
@@ -77,7 +85,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/category"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginBottom="4dp" />
<TextView
@@ -85,17 +93,17 @@
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/thumb"
android:layout_alignParentRight="true"
android:layout_marginRight="6dp"
android:layout_alignBottom="@id/thumb_card"
android:layout_alignParentEnd="true"
android:layout_marginEnd="6dp"
android:layout_marginBottom="4dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/posted"
android:layout_alignParentRight="true"
android:layout_marginRight="6dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="6dp"
android:divider="@drawable/spacer_x6"
android:gravity="center_vertical"
android:orientation="horizontal"
@@ -19,15 +19,21 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.hippo.ehviewer.widget.FixedThumb xmlns:auto="http://schemas.android.com/apk/res-auto"
android:id="@+id/image"
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
auto:aspect="0.667"
auto:maxAspect="0.8"
auto:minAspect="0.5"
auto:retryType="longClick" />
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb xmlns:auto="http://schemas.android.com/apk/res-auto"
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
auto:aspect="0.667"
auto:maxAspect="0.8"
auto:minAspect="0.5"
auto:retryType="longClick" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/text"
@@ -27,41 +27,4 @@
android:scaleType="centerCrop"
app:srcCompat="@drawable/sadpanda_low_poly" />
<com.hippo.widget.LoadImageView
android:id="@+id/avatar"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="bottom"
android:layout_marginStart="16dp"
android:layout_marginBottom="48dp" />
<TextView
android:id="@+id/display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:textColor="@android:color/white"
android:textSize="@dimen/text_small" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:background="@android:color/transparent"
android:clipToPadding="false"
android:padding="8dp">
<ImageView
android:id="@+id/night_mode"
android:layout_width="36dp"
android:layout_height="36dp"
android:clickable="true"
android:focusable="true"
android:padding="6dp"
app:srcCompat="@drawable/ic_baseline_dark_mode_24"
app:tint="#fff" />
</FrameLayout>
</FrameLayout>
+3 -3
View File
@@ -20,11 +20,11 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.hippo.ehviewer.widget.CategoryTable
android:id="@+id/search_category_table"
<com.google.android.material.chip.ChipGroup
android:id="@+id/search_category_chipgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/search_item_interval" />
android:layout_marginBottom="@dimen/search_item_interval"/>
<com.hippo.widget.RadioGridGroup
android:id="@+id/normal_search_mode"
@@ -1,149 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 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
~ 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">
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/doujinshi"
android:text="@string/doujinshi"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/manga"
android:text="@string/manga"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/artist_cg"
android:text="@string/artist_cg"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/game_cg"
android:text="@string/game_cg"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/western"
android:text="@string/western"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/non_h"
android:text="@string/non_h"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/image_set"
android:text="@string/image_set"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/cosplay"
android:text="@string/cosplay"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/asian_porn"
android:text="@string/asian_porn"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/misc"
android:text="@string/misc"
android:textColor="@android:color/white" />
</TableRow>
</merge>
+18 -11
View File
@@ -16,19 +16,26 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<com.hippo.widget.FixedAspectImageView
android:id="@+id/preview"
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:maxWidth="@dimen/image_search_max_size"
android:maxHeight="@dimen/image_search_max_size"
android:visibility="gone" />
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.MaterialComponents.Button"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -44,7 +51,7 @@
android:id="@+id/search_uss"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/advance_search_table_item_margin"
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="true"
android:minWidth="0dp"
@@ -55,7 +62,7 @@
android:id="@+id/search_osc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false"
android:minWidth="0dp"
@@ -69,7 +76,7 @@
android:id="@+id/search_se"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/advance_search_table_item_margin"
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false"
android:minWidth="0dp"
@@ -79,7 +86,7 @@
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false" />
+2 -1
View File
@@ -52,7 +52,8 @@
<!-- CategoryText -->
<style name="CategoryText">
<item name="android:textAppearance">@style/TextAppearance.Material3.ActionBar.Subtitle</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.ActionBar.Subtitle
</item>
<item name="android:textStyle">bold</item>
<item name="android:gravity">center</item>
<item name="android:minHeight">@dimen/category_table_item_height</item>