From 029bca22ed88f23b980ea0c3aa0f61937fa84489 Mon Sep 17 00:00:00 2001 From: seven332 Date: Sat, 24 Mar 2018 02:17:58 +0800 Subject: [PATCH] Add hosts UI --- app/src/main/AndroidManifest.xml | 6 + .../com/hippo/ehviewer/EhApplication.java | 12 + .../java/com/hippo/ehviewer/client/EhDns.java | 59 ++++ .../com/hippo/ehviewer/ui/HostsActivity.java | 272 ++++++++++++++++++ app/src/main/res/layout/activity_hosts.xml | 56 ++++ app/src/main/res/layout/dialog_hosts.xml | 54 ++++ app/src/main/res/layout/item_hosts.xml | 40 +++ app/src/main/res/values-zh-rCN/strings.xml | 15 + app/src/main/res/values-zh-rHK/strings.xml | 15 + app/src/main/res/values-zh-rTW/strings.xml | 15 + app/src/main/res/values/strings.xml | 15 + app/src/main/res/xml/advanced_settings.xml | 7 + .../com/hippo/ehviewer/ExampleUnitTest.java | 15 - 13 files changed, 566 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/client/EhDns.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/HostsActivity.java create mode 100644 app/src/main/res/layout/activity_hosts.xml create mode 100644 app/src/main/res/layout/dialog_hosts.xml create mode 100644 app/src/main/res/layout/item_hosts.xml delete mode 100644 app/src/test/java/com/hippo/ehviewer/ExampleUnitTest.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 14e20d468..d7067dbd3 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,12 @@ android:theme="@style/AppTheme.NoActionBar" android:label="@string/u_config"/> + + diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 4eb273b9f..c783188f5 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -31,6 +31,7 @@ import com.hippo.beerbelly.SimpleDiskCache; import com.hippo.conaco.Conaco; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhCookieStore; +import com.hippo.ehviewer.client.EhDns; import com.hippo.ehviewer.client.EhEngine; import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.download.DownloadManager; @@ -81,6 +82,7 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx private LruCache mGalleryDetailCache; private SimpleDiskCache mSpiderInfoCache; private DownloadManager mDownloadManager; + private Hosts mHosts; private final List mActivityList = new ArrayList<>(); @@ -238,6 +240,7 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .cookieJar(getEhCookieStore(application)) + .dns(new EhDns(application)) .build(); } return application.mOkHttpClient; @@ -303,6 +306,15 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx return application.mDownloadManager; } + @NonNull + public static Hosts getHosts(@NonNull Context context) { + EhApplication application = ((EhApplication) context.getApplicationContext()); + if (application.mHosts == null) { + application.mHosts = new Hosts(application, "hosts.db"); + } + return application.mHosts; + } + private boolean handleException(Throwable ex) { if (ex == null) { return false; diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhDns.java b/app/src/main/java/com/hippo/ehviewer/client/EhDns.java new file mode 100644 index 000000000..2472d95d4 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/EhDns.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018 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.client; + +/* + * Created by Hippo on 2018/3/23. + */ + +import android.content.Context; +import com.hippo.ehviewer.EhApplication; +import com.hippo.ehviewer.Hosts; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import okhttp3.Dns; + +public class EhDns implements Dns { + + private final Hosts hosts; + + public EhDns(Context context) { + hosts = EhApplication.getHosts(context); + } + + @Override + public List lookup(String hostname) throws UnknownHostException { + if (hostname == null) throw new UnknownHostException("hostname == null"); + + InetAddress inetAddress = hosts.get(hostname); + if (inetAddress != null) { + return Collections.singletonList(inetAddress); + } + + try { + return Arrays.asList(InetAddress.getAllByName(hostname)); + } catch (NullPointerException e) { + UnknownHostException unknownHostException = + new UnknownHostException("Broken system behaviour for dns lookup of " + hostname); + unknownHostException.initCause(e); + throw unknownHostException; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/HostsActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/HostsActivity.java new file mode 100644 index 000000000..4c382bec1 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/HostsActivity.java @@ -0,0 +1,272 @@ +/* + * Copyright 2018 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +/* + * Created by Hippo on 2018/3/23. + */ + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TextInputLayout; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.hippo.easyrecyclerview.EasyRecyclerView; +import com.hippo.easyrecyclerview.LinearDividerItemDecoration; +import com.hippo.ehviewer.EhApplication; +import com.hippo.ehviewer.Hosts; +import com.hippo.ehviewer.R; +import com.hippo.ripple.Ripple; +import com.hippo.yorozuya.LayoutUtils; +import java.util.List; +import java.util.Locale; + +public class HostsActivity extends ToolbarActivity + implements EasyRecyclerView.OnItemClickListener, View.OnClickListener { + + private static final String DIALOG_TAG_ADD_HOST = AddHostDialogFragment.class.getName(); + private static final String DIALOG_TAG_EDIT_HOST = EditHostDialogFragment.class.getName(); + + private static final String KEY_HOST = "com.hippo.ehviewer.ui.HostsActivity.HOST"; + private static final String KEY_IP = "com.hippo.ehviewer.ui.HostsActivity.IP"; + + private Hosts hosts; + private List> data; + + private EasyRecyclerView recyclerView; + private View tip; + private HostsAdapter adapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + hosts = EhApplication.getHosts(this); + data = hosts.getAll(); + + setContentView(R.layout.activity_hosts); + setNavigationIcon(R.drawable.v_arrow_left_dark_x24); + recyclerView = findViewById(R.id.recycler_view); + tip = findViewById(R.id.tip); + FloatingActionButton fab = findViewById(R.id.fab); + + adapter = new HostsAdapter(); + recyclerView.setAdapter(adapter); + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + LinearDividerItemDecoration decoration = new LinearDividerItemDecoration( + LinearDividerItemDecoration.VERTICAL, + getResources().getColor(R.color.divider), + LayoutUtils.dp2pix(this, 1)); + decoration.setShowLastDivider(true); + recyclerView.addItemDecoration(decoration); + recyclerView.setSelector(Ripple.generateRippleDrawable(this, false)); + recyclerView.setHasFixedSize(true); + recyclerView.setOnItemClickListener(this); + recyclerView.setPadding( + recyclerView.getPaddingLeft(), + recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + recyclerView.getPaddingBottom() + getResources().getDimensionPixelOffset(R.dimen.gallery_padding_bottom_fab)); + + fab.setOnClickListener(this); + + recyclerView.setVisibility(data.isEmpty() ? View.GONE : View.VISIBLE); + tip.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public boolean onItemClick(EasyRecyclerView easyRecyclerView, View view, int position, long id) { + Pair pair = data.get(position); + Bundle args = new Bundle(); + args.putString(KEY_HOST, pair.first); + args.putString(KEY_IP, pair.second); + + DialogFragment fragment = new EditHostDialogFragment(); + fragment.setArguments(args); + fragment.show(getSupportFragmentManager(), DIALOG_TAG_EDIT_HOST); + + return true; + } + + @Override + public void onClick(View v) { + new AddHostDialogFragment().show(getSupportFragmentManager(), DIALOG_TAG_ADD_HOST); + } + + private void notifyHostsChanges() { + data = hosts.getAll(); + recyclerView.setVisibility(data.isEmpty() ? View.GONE : View.VISIBLE); + tip.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE); + adapter.notifyDataSetChanged(); + } + + private class HostsHolder extends RecyclerView.ViewHolder { + + public final TextView host; + public final TextView ip; + + public HostsHolder(View itemView) { + super(itemView); + host = itemView.findViewById(R.id.host); + ip = itemView.findViewById(R.id.ip); + } + } + + private class HostsAdapter extends RecyclerView.Adapter { + + private final LayoutInflater inflater = getLayoutInflater(); + + @NonNull + @Override + public HostsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull HostsHolder holder, int position) { + Pair pair = data.get(position); + holder.host.setText(pair.first); + holder.ip.setText(pair.second); + } + + @Override + public int getItemCount() { + return data.size(); + } + } + + public abstract static class HostDialogFragment extends DialogFragment { + + private TextView host; + private TextView ip; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_hosts, null, false); + host = view.findViewById(R.id.host); + ip = view.findViewById(R.id.ip); + + Bundle arguments = getArguments(); + if (savedInstanceState == null && arguments != null) { + host.setText(arguments.getString(KEY_HOST)); + ip.setText(arguments.getString(KEY_IP)); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()).setView(view); + onCreateDialogBuilder(builder); + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d)); + + return dialog; + } + + protected abstract void onCreateDialogBuilder(AlertDialog.Builder builder); + + protected abstract void onCreateDialog(AlertDialog dialog); + + protected void put(AlertDialog dialog) { + TextView host = dialog.findViewById(R.id.host); + TextView ip = dialog.findViewById(R.id.ip); + String hostString = host.getText().toString().trim().toLowerCase(Locale.US); + String ipString = ip.getText().toString().trim(); + + if (!Hosts.isValidHost(hostString)) { + TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout); + hostInputLayout.setError(getContext().getString(R.string.invalid_host)); + return; + } + + if (!Hosts.isValidIp(ipString)) { + TextInputLayout ipInputLayout = dialog.findViewById(R.id.ip_input_layout); + ipInputLayout.setError(getContext().getString(R.string.invalid_ip)); + return; + } + + HostsActivity activity = (HostsActivity) dialog.getOwnerActivity(); + activity.hosts.put(hostString, ipString); + activity.notifyHostsChanges(); + + dialog.dismiss(); + } + + protected void delete(AlertDialog dialog) { + TextView host = dialog.findViewById(R.id.host); + String hostString = host.getText().toString().trim().toLowerCase(Locale.US); + + HostsActivity activity = (HostsActivity) dialog.getOwnerActivity(); + activity.hosts.delete(hostString); + activity.notifyHostsChanges(); + + dialog.dismiss(); + } + } + + public static class AddHostDialogFragment extends HostDialogFragment { + + @Override + protected void onCreateDialogBuilder(AlertDialog.Builder builder) { + builder.setTitle(R.string.add_host); + builder.setPositiveButton(R.string.add_host_add, null); + } + + @Override + protected void onCreateDialog(AlertDialog dialog) { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog)); + } + } + + public static class EditHostDialogFragment extends HostDialogFragment { + + @Override + protected void onCreateDialogBuilder(AlertDialog.Builder builder) { + builder.setTitle(R.string.edit_host); + builder.setPositiveButton(R.string.edit_host_confirm, null); + builder.setNegativeButton(R.string.edit_host_delete, null); + } + + @Override + protected void onCreateDialog(AlertDialog dialog) { + dialog.findViewById(R.id.host_input_layout).setEnabled(false); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog)); + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> delete(dialog)); + } + } +} diff --git a/app/src/main/res/layout/activity_hosts.xml b/app/src/main/res/layout/activity_hosts.xml new file mode 100644 index 000000000..85deede4e --- /dev/null +++ b/app/src/main/res/layout/activity_hosts.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_hosts.xml b/app/src/main/res/layout/dialog_hosts.xml new file mode 100644 index 000000000..6e1a18a78 --- /dev/null +++ b/app/src/main/res/layout/dialog_hosts.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_hosts.xml b/app/src/main/res/layout/item_hosts.xml new file mode 100644 index 000000000..98047bdff --- /dev/null +++ b/app/src/main/res/layout/item_hosts.xml @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b22fc099f..34160ccc1 100755 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -355,6 +355,19 @@ 应用 点击右上角的对勾来保存设置 + + Hosts + hosts.txt + Host + IP + 添加 host + 添加 + 编辑 host + 确认 + 删除 + 无效的 host + 无效的 IP + 第 %d 页 @string/refresh @@ -480,6 +493,8 @@ 清理内存缓存 清理缩略图、画廊信息的内存缓存 阅读缓存大小 + Hosts + 将主机名称映射到相应的IP地址 图案保护 未设置图案保护 已设置图案保护 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 24d4454e0..9d1b59ed7 100755 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -355,6 +355,19 @@ 應用 點擊右上角的對勾來保存設置 + + Hosts + hosts.txt + Host + IP + 添加 host + 添加 + 編輯 host + 確認 + 刪除 + 無效的 host + 無效的 IP + 第 %d 頁 @string/refresh @@ -480,6 +493,8 @@ 清理內存緩存 清理縮略圖、畫廊信息的內存緩存 閲讀緩存大小 + Hosts + 將主機名稱對映到相應的IP位址 圖案保護 未設置圖案保護 已設置圖案保護 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 66cbc724b..c0551d44d 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -355,6 +355,19 @@ 應用 點選右上角的對勾來儲存設定 + + Hosts + hosts.txt + Host + IP + 添加 host + 添加 + 編輯 host + 確認 + 刪除 + 無效的 host + 無效的 IP + 第 %d 頁 @string/refresh @@ -480,6 +493,8 @@ 清除記憶體快取 清除縮圖、圖庫詳細資料的記憶體快取 閱讀快取大小 + Hosts + 將主機名稱對映到相應的IP位址 繪圖解鎖 尚未設定繪圖解鎖 已設定繪圖解鎖 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a85315737..b60a22280 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -399,6 +399,19 @@ Apply Click the check mark to save the settings + + Hosts + hosts.txt + Host + IP + Add a host + Add + Edit the host + Confirm + Delete + Invalid host + Invalid IP + Page %d @string/refresh @@ -523,6 +536,8 @@ Clear memory cache Clear thumb, gallery info memory cache Read cache size + Hosts + Map hostnames to IP addresses Pattern protection Pattern protection has not been set Pattern protection has been set diff --git a/app/src/main/res/xml/advanced_settings.xml b/app/src/main/res/xml/advanced_settings.xml index 3d57ca03c..945271f55 100644 --- a/app/src/main/res/xml/advanced_settings.xml +++ b/app/src/main/res/xml/advanced_settings.xml @@ -48,6 +48,13 @@ app:entryValues="@array/read_cache_size_entry_values" android:defaultValue="160"/> + +