diff --git a/webpagesources/build.gradle b/webpagesources/build.gradle
index 17bdeac..c60c752 100644
--- a/webpagesources/build.gradle
+++ b/webpagesources/build.gradle
@@ -46,6 +46,9 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
+ api 'io.github.medyo:android-about-page:2.0.0'
+ // 视图布局下拉控件
+ api 'com.baoyz.pullrefreshlayout:library:1.2.0'
// SSH
api 'com.jcraft:jsch:0.1.55'
// Html 解析
@@ -65,7 +68,7 @@ dependencies {
//api 'androidx.viewpager:viewpager:1.0.0'
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
- //api 'androidx.fragment:fragment:1.1.0'
+ api 'androidx.fragment:fragment:1.1.0'
api 'cc.winboll.studio:libaes:15.8.0'
api 'cc.winboll.studio:libapputils:15.8.2'
diff --git a/webpagesources/build.properties b/webpagesources/build.properties
index d4b5653..567e171 100644
--- a/webpagesources/build.properties
+++ b/webpagesources/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Tue Jun 10 04:31:40 GMT 2025
+#Tue Jun 10 06:38:52 GMT 2025
stageCount=0
libraryProject=
baseVersion=15.0
publishVersion=15.0.0
-buildCount=1
+buildCount=6
baseBetaVersion=15.0.1
diff --git a/webpagesources/src/main/AndroidManifest.xml b/webpagesources/src/main/AndroidManifest.xml
index 3952076..e1ef3a4 100644
--- a/webpagesources/src/main/AndroidManifest.xml
+++ b/webpagesources/src/main/AndroidManifest.xml
@@ -3,18 +3,31 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.webpagesources">
+
+
+
+
+
+
+
+
+
+ android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config">
+ android:label="@string/app_name"
+ android:launchMode="standard"
+ android:exported="true">
@@ -26,6 +39,9 @@
+
+
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/MainActivity.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/MainActivity.java
index 6a99829..362aad7 100644
--- a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/MainActivity.java
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/MainActivity.java
@@ -1,26 +1,123 @@
package cc.winboll.studio.webpagesources;
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/07/02 17:10:46
+ * @Describe 主要启动窗口类
+ */
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.webkit.ValueCallback;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import cc.winboll.studio.libappbase.GlobalApplication;
+import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
-import com.hjq.toast.ToastUtils;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+import cc.winboll.studio.webpagesources.R;
+import cc.winboll.studio.webpagesources.activities.AboutActivity;
+import cc.winboll.studio.webpagesources.fragment.SourcesFragment;
+import cc.winboll.studio.webpagesources.fragment.WebFragment;
-public class MainActivity extends AppCompatActivity {
+public class MainActivity extends AppCompatActivity implements IWinBoLLActivity {
+
+ public static final String TAG = "MainActivity";
+
+ public static final int REQUEST_ABOUT_ACTIVITY = 0;
+ public static final int REQUEST_FILE_CHOOSER = 1;
+
+ public static final String MSG_UPDATE_URL = "MSG_UPDATE_URL";
LogView mLogView;
+ WebFragment mWebFragment;
+ SourcesFragment mSourcesFragment;
+ static boolean _mIsLoadedHomePage;
+
+ @Override
+ public Activity getActivity() {
+ return this;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
-
- Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
-
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
mLogView = findViewById(R.id.logview);
-
- ToastUtils.show("onCreate");
+ mLogView.start();
+
+ FragmentTransaction ft = ((FragmentManager)getSupportFragmentManager()).beginTransaction();
+ mSourcesFragment = new SourcesFragment();
+ ft.add(R.id.activitymainFrameLayout1, mSourcesFragment, SourcesFragment.TAG);
+ ft.hide(mSourcesFragment);
+ mWebFragment = new WebFragment();
+ //mWebFragment.setJSOnShowSourceListener(this);
+ ft.add(R.id.activitymainFrameLayout1, mWebFragment, WebFragment.TAG);
+ ft.show(mWebFragment);
+ ft.commit();
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ if (_mIsLoadedHomePage) {
+ //ToastUtils.show("重新加载当前页面");
+ mWebFragment.reloadLastUrl();
+ } else {
+ //ToastUtils.show("加载默认主页");
+ mWebFragment.loadUrl(getApplicationContext().getString(R.string.app_homepage));
+ _mIsLoadedHomePage = true;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.toolbar_main, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ //return super.onOptionsItemSelected(item);
+ switch (item.getItemId()) {
+ case R.id.item_sources : {
+ LogUtils.d(TAG, "item_web");
+ FragmentTransaction ft = ((FragmentManager)getSupportFragmentManager()).beginTransaction();
+ ft.hide(mWebFragment);
+ ft.show(mSourcesFragment);
+ ft.commit();
+ break;
+ }
+ case R.id.item_web : {
+ LogUtils.d(TAG, "item_sources");
+ FragmentTransaction ft = ((FragmentManager)getSupportFragmentManager()).beginTransaction();
+ ft.hide(mSourcesFragment);
+ ft.show(mWebFragment);
+ ft.commit();
+ break;
+ }
+ case R.id.item_editor : {
+ mSourcesFragment.shareHtml();
+ break;
+ }
+ case R.id.item_about : {
+ GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, AboutActivity.class);
+ break;
+ }
+ }
+ return super.onOptionsItemSelected(item);
}
@Override
@@ -28,4 +125,41 @@ public class MainActivity extends AppCompatActivity {
super.onResume();
mLogView.start();
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ //ToastUtils.show("onActivityResult Main");
+ switch (requestCode) {
+ case REQUEST_ABOUT_ACTIVITY : {
+ LogUtils.d(TAG, "REQUEST_ABOUT_ACTIVITY");
+ break;
+ }
+ case REQUEST_FILE_CHOOSER : {
+ //ToastUtils.show("MainActivity.REQUEST_FILE_CHOOSER");
+ ValueCallback filePathCallback = mWebFragment.getWebView().getFilePathCallback();
+
+ if (filePathCallback == null) {
+ return;
+ }
+
+ Uri[] results = null;
+ // 检查选择的文件是否为空
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ String dataString = data.getDataString();
+ if (dataString != null) {
+ results = new Uri[]{Uri.parse(dataString)};
+ }
+ }
+
+ // 传递选择的文件给WebView
+ filePathCallback.onReceiveValue(results);
+ filePathCallback = null;
+
+ break;
+ }
+ default : {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/activities/AboutActivity.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/activities/AboutActivity.java
new file mode 100644
index 0000000..00a359e
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/activities/AboutActivity.java
@@ -0,0 +1,53 @@
+package cc.winboll.studio.webpagesources.activities;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2024/07/14 13:20:33
+ * @Describe AboutFragment Test
+ */
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import androidx.appcompat.app.AppCompatActivity;
+import cc.winboll.studio.libappbase.GlobalApplication;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+import cc.winboll.studio.webpagesources.R;
+import com.hjq.toast.ToastUtils;
+
+final public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity {
+
+ public static final String TAG = "AboutActivity";
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ public Activity getActivity() {
+ return this;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_about);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.toolbar_about, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.item_help) {
+ ToastUtils.show("R.id.item_help");
+ } else if (item.getItemId() == android.R.id.home) {
+ GlobalApplication.getWinBoLLActivityManager().getInstance().finish(this);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/AuthLoginDialog.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/AuthLoginDialog.java
new file mode 100644
index 0000000..84b8fe3
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/AuthLoginDialog.java
@@ -0,0 +1,197 @@
+package cc.winboll.studio.webpagesources.common;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2024/08/26 20:48:46
+ * @Describe 登录验证对话框
+ */
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.HttpAuthHandler;
+import android.webkit.WebView;
+import android.widget.EditText;
+import android.widget.TextView;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.PopupMenu;
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog;
+import cc.winboll.studio.webpagesources.R;
+import cc.winboll.studio.webpagesources.models.AuthenticationBean;
+import cc.winboll.studio.webpagesources.common.AuthenticationUtils;
+import com.hjq.toast.ToastUtils;
+import java.util.ArrayList;
+
+public class AuthLoginDialog {
+
+ public static final String TAG = "AuthLoginDialog";
+
+ WebView mWebView;
+ static AuthenticationBean _mLastAuthenticationBean;
+ EditText metUserName;
+ EditText metPassword;
+ AlertDialog mLoginAlertDialog;
+ HttpAuthHandler mHttpAuthHandler;
+
+ public AuthLoginDialog(WebView view, HttpAuthHandler httpAuthHandler, String host, String realm) {
+ mWebView = view;
+ mHttpAuthHandler = httpAuthHandler;
+ _mLastAuthenticationBean = new AuthenticationBean();
+ _mLastAuthenticationBean.setHost(host);
+ _mLastAuthenticationBean.setRealm(realm);
+ }
+
+ public void show() {
+ // 获取要登录的URL和已知的身份验证信息
+ String authUrl = "[ " + _mLastAuthenticationBean.getHost() + " ] " + _mLastAuthenticationBean.getRealm();
+ final AuthenticationUtils authenticationUtils = AuthenticationUtils.getInstance(mWebView.getContext());
+ final ArrayList listInfo = authenticationUtils.getHostAuthenticationList(_mLastAuthenticationBean.getHost(), _mLastAuthenticationBean.getRealm());
+
+ final View llMain = mWebView.inflate(mWebView.getContext(), R.layout.dialog_login_auth, null);
+ metUserName = llMain.findViewById(R.id.viewloginhttpEditText1);
+ metPassword = llMain.findViewById(R.id.viewloginhttpEditText2);
+ RecyclerView recyclerView = llMain.findViewById(R.id.dialogloginauthRecyclerView1);
+ recyclerView.setLayoutManager(new LinearLayoutManager(mWebView.getContext(), LinearLayoutManager.HORIZONTAL, false));
+ AuthenticationBeanListAdapter adapter = new AuthenticationBeanListAdapter(listInfo);
+ // 设置 RecyclerView 的适配器
+ recyclerView.setAdapter(adapter);
+
+ if (listInfo.size() > 0) {
+ metUserName.setText(listInfo.get(0).getUserName());
+ metPassword.setText(listInfo.get(0).getPassword());
+ LogUtils.d(TAG, "listInfo setText");
+ }
+ // 弹窗提示用户输入账号密码
+ mLoginAlertDialog = new AlertDialog.Builder(mWebView.getContext())
+ .setTitle("Login Required")
+ .setMessage(authUrl + "\nPlease enter your credentials:")
+ .setView(llMain)
+ .setPositiveButton("OK", new DialogInterface.OnClickListener(){
+ @Override
+ public void onClick(DialogInterface dialogInterface, int p) {
+ _mLastAuthenticationBean.setUserName(metUserName.getText().toString());
+ _mLastAuthenticationBean.setPassword(metPassword.getText().toString());
+ //LogUtils.d(TAG, "getUserName : " + _mLastAuthenticationBean.getUserName());
+ //LogUtils.d(TAG, "getPassword : " + _mLastAuthenticationBean.getPassword());
+ //LogUtils.d(TAG, "mHttpAuthHandler : " + mHttpAuthHandler);
+ // 进入网站
+ mHttpAuthHandler.proceed(_mLastAuthenticationBean.getUserName(), _mLastAuthenticationBean.getPassword());
+ }
+ })
+ .show();
+ }
+
+ public void checkAndSaveLastAuthenticationBean() {
+ if (_mLastAuthenticationBean != null) {
+ // 更新身份验证信息
+ if (!_mLastAuthenticationBean.getHost().isEmpty()
+ && !_mLastAuthenticationBean.getRealm().isEmpty()
+ && !_mLastAuthenticationBean.getUserName().isEmpty()
+ && !_mLastAuthenticationBean.getPassword().isEmpty()) {
+ final AuthenticationUtils authenticationUtils = AuthenticationUtils.getInstance(mWebView.getContext());
+ authenticationUtils.saveAuthenticationInfo(_mLastAuthenticationBean);
+ // 清理已保存,或者不需要保存的登录信息
+ _mLastAuthenticationBean = null;
+ }
+ }
+ }
+
+ class AuthenticationBeanListAdapter extends RecyclerView.Adapter {
+
+ ArrayList mDataList;
+ public AuthenticationBeanListAdapter(ArrayList listInfo) {
+ mDataList = listInfo;
+ }
+ @Override
+ public int getItemCount() {
+ return mDataList.size();
+ }
+
+ void deleteSMSRecycleItem(final int position) {
+ YesNoAlertDialog.show(mWebView.getContext(),
+ "密码删除提示",
+ "请确认删除(" + mDataList.get(position).getUserName() + ")密码!"
+ , (new YesNoAlertDialog.OnDialogResultListener(){
+
+ @Override
+ public void onYes() {
+ AuthenticationUtils authenticationUtils = AuthenticationUtils.getInstance(mWebView.getContext());
+ authenticationUtils.deleteAuthenticationInfo(mDataList.get(position));
+ mDataList.remove(position);
+ notifyDataSetChanged();
+ ToastUtils.show("密码已删除!");
+ }
+
+ @Override
+ public void onNo() {
+
+ }
+ }));
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
+ final AuthenticationBean item = mDataList.get(position);
+ if (holder.getItemViewType() == 0) {
+ final SimpleViewHolder viewHolder = (SimpleViewHolder) holder;
+ viewHolder.mtvUserName.setText(item.getUserName());
+ viewHolder.mCardView.setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ metUserName.setText(item.getUserName());
+ metPassword.setText(item.getPassword());
+ }
+ });
+ viewHolder.mCardView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View p1) {
+ // 弹出复制菜单
+ PopupMenu menu = new PopupMenu(mWebView.getContext(), viewHolder.mCardView);
+ //加载菜单资源
+ menu.getMenuInflater().inflate(R.menu.toolbar_authinfo, menu.getMenu());
+ //设置点击事件的响应
+ menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ int nItemId = menuItem.getItemId();
+ if (nItemId == R.id.item_delete_authinfo) {
+ deleteSMSRecycleItem(position);
+ }
+ return true;
+ }
+ });
+ //一定要调用show()来显示弹出式菜单
+ menu.show();
+
+ return true;
+ }
+ });
+ }
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == 0) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_authinfo, parent, false);
+ return new SimpleViewHolder(view);
+ }
+ return null;
+ }
+
+ class SimpleViewHolder extends RecyclerView.ViewHolder {
+ TextView mtvUserName;
+ CardView mCardView;
+ SimpleViewHolder(View itemView) {
+ super(itemView);
+ mtvUserName = itemView.findViewById(R.id.viewitemauthinfoTextView1);
+ mCardView = itemView.findViewById(R.id.listviewauthinfoCardView1);
+ }
+ }
+
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/AuthenticationUtils.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/AuthenticationUtils.java
new file mode 100644
index 0000000..ff6ac3a
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/AuthenticationUtils.java
@@ -0,0 +1,131 @@
+package cc.winboll.studio.webpagesources.common;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2024/08/26 16:16:00
+ * @Describe 网站登录验证工具类
+ */
+import android.content.Context;
+import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog;
+import cc.winboll.studio.webpagesources.models.AuthenticationBean;
+import java.io.File;
+import java.util.ArrayList;
+
+public class AuthenticationUtils {
+
+ public static final String TAG = "AuthenticationUtils";
+
+ static AuthenticationUtils _mAuthenticationUtils;
+ static String _mBeanPath;
+ Context mContext;
+ ArrayList mData;
+
+ AuthenticationUtils(Context context) {
+ mContext = context;
+ File beanDir = new File(context.getDataDir(), TAG);
+ if (!beanDir.exists()) {
+ beanDir.mkdirs();
+ }
+ _mBeanPath = beanDir.getPath() + "/" + AuthenticationBean.class.getName() + ".json";
+ mData = new ArrayList();
+ AuthenticationBean.loadBeanListFromFile(_mBeanPath, mData, AuthenticationBean.class);
+ }
+
+ public static AuthenticationUtils getInstance(Context context) {
+ if (_mAuthenticationUtils == null) {
+ _mAuthenticationUtils = new AuthenticationUtils(context);
+ }
+ return _mAuthenticationUtils;
+ }
+
+ ArrayList loadAuthenticationData(String szHost, String szRealm) {
+ ArrayList listReturn = new ArrayList();
+ for (AuthenticationBean item : getInstance(mContext).mData) {
+ if (szHost.equals(item.getHost())
+ && szRealm.equals(item.getRealm())) {
+ listReturn.add(item);
+ }
+ }
+ return listReturn;
+ }
+
+ public ArrayList getHostAuthenticationList(String szHost, String szRealm) {
+ ArrayList listReturn = new ArrayList();
+ for (final AuthenticationBean item : mData) {
+ if (szHost.equals(item.getHost())
+ && szRealm.equals(item.getRealm())) {
+ listReturn.add(item);
+ }
+ }
+ return listReturn;
+ }
+
+ public void deleteAuthenticationInfo(AuthenticationBean bean) {
+ for (final AuthenticationBean item : mData) {
+ if (bean.getHost().equals(item.getHost())
+ && bean.getRealm().equals(item.getRealm())
+ && bean.getUserName().equals(item.getUserName())) {
+ mData.remove(item);
+ AuthenticationBean.saveBeanListToFile(_mBeanPath, mData);
+ return;
+ }
+ }
+ }
+
+ public void putAuthenticationBeanToTheFirstPosition(AuthenticationBean bean) {
+ for (AuthenticationBean item : mData) {
+ if (bean.getHost().equals(item.getHost())
+ && bean.getRealm().equals(item.getRealm())
+ && bean.getUserName().equals(item.getUserName())) {
+ mData.add(0, new AuthenticationBean(item));
+ mData.remove(item);
+ AuthenticationBean.saveBeanListToFile(_mBeanPath, mData);
+ return;
+ }
+ }
+ }
+
+ public void saveAuthenticationInfo(AuthenticationBean bean) {
+ saveAuthenticationInfo(bean.getHost(), bean.getRealm(), bean.getUserName(), bean.getPassword());
+ }
+
+ public void saveAuthenticationInfo(String szHost, String szRealm, String szUserName, final String szPassword) {
+ boolean isFindUserName = false;
+ AuthenticationBean.loadBeanListFromFile(_mBeanPath, mData, AuthenticationBean.class);
+ for (final AuthenticationBean item : mData) {
+ if (szHost.equals(item.getHost())
+ && szRealm.equals(item.getRealm())
+ && szUserName.equals(item.getUserName())) {
+ isFindUserName = true;
+ if (!szPassword.equals(item.getPassword())) {
+ // 如果找到同名信息,就提示是否更新
+ YesNoAlertDialog.show(mContext, "Ask Update Password", "Is update " + item.getUserName() + " Password?", new YesNoAlertDialog.OnDialogResultListener(){
+ @Override
+ public void onNo() {
+ return;
+ }
+
+ @Override
+ public void onYes() {
+ item.setPassword(szPassword);
+ AuthenticationBean.saveBeanListToFile(_mBeanPath, mData);
+ putAuthenticationBeanToTheFirstPosition(item);
+ return;
+ }
+ });
+ } else {
+ putAuthenticationBeanToTheFirstPosition(item);
+ return;
+ }
+ }
+ }
+
+ // 如果找不到同名信息,就新建一个
+ if (!isFindUserName) {
+ AuthenticationBean bean = new AuthenticationBean(szHost, szRealm, szUserName, szPassword);
+ mData.add(bean);
+ AuthenticationBean.saveBeanListToFile(_mBeanPath, mData);
+ putAuthenticationBeanToTheFirstPosition(bean);
+ }
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/BaseDownLoadThread.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/BaseDownLoadThread.java
new file mode 100644
index 0000000..1373cef
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/BaseDownLoadThread.java
@@ -0,0 +1,242 @@
+package cc.winboll.studio.webpagesources.common;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/06/22 15:01:43
+ * @Describe 下载线程基类
+ */
+import android.content.Context;
+import cc.winboll.studio.libappbase.LogUtils;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+abstract public class BaseDownLoadThread implements Runnable {
+
+ public static final String TAG = "BaseDownLoadThread";
+
+ Context mContext;
+ //UserLogonUtil mUserLogonUtil;
+ String mszUrl;
+ String mszDowndloadName;
+ protected File mfDownloadDir;
+ protected File mfDownloadFile;
+
+ //
+ // 构造函数
+ //
+ public BaseDownLoadThread(Context context) {
+
+ }
+
+ //
+ // 构造函数
+ //
+ public BaseDownLoadThread(Context context, String szUrl) {
+ mContext = context;
+ File fDownloadDir = new File(context.getExternalCacheDir(), "Download");
+ initDownLoadEnvironment(fDownloadDir, szUrl);
+ }
+
+
+ //
+ // 构造函数
+ //
+ public BaseDownLoadThread(Context context, String szUrl, String szDowndloadName) {
+ mContext = context;
+ mszDowndloadName = szDowndloadName;
+ File fDownloadDir = new File(context.getExternalCacheDir(), "Download");
+ initDownLoadEnvironment(fDownloadDir, szUrl);
+ }
+
+ //
+ // 构造函数
+ //
+ public BaseDownLoadThread(File fDownloadDir, String szUrl) {
+ initDownLoadEnvironment(fDownloadDir, szUrl);
+ }
+
+ //
+ // 初始化下载环境参数
+ //
+ void initDownLoadEnvironment(File fDownloadDir, String szUrl) {
+ mfDownloadDir = fDownloadDir;
+ //LogUtils.d(TAG, "mfDownloadDir is : " + mfDownloadDir);
+ if (!mfDownloadDir.exists()) {
+ mfDownloadDir.mkdir();
+ }
+ this.mszUrl = szUrl;
+ //LogUtils.d(TAG, "mszUrl is : " + mszUrl);
+ }
+
+ @Override
+ public void run() {
+ //LogUtils.d(TAG, "DownLoad Start : " + mszUrl);
+ InputStream in = null;
+ FileOutputStream fout = null;
+ try {
+ //LogUtils.d(TAG, "mszUrl is " + mszUrl);
+ URL httpUrl = new URL(mszUrl);
+ HttpURLConnection conn = (HttpURLConnection) httpUrl.openConnection();
+ //conn.setRequestProperty(UserLogonUtil.getInstance(mContext).getWinBollAppHeaderKey(),
+ // UserLogonUtil.getInstance(mContext).getWinBollAppHeaderValue());
+ conn.setDoInput(true);
+ conn.setDoOutput(false);
+ in = conn.getInputStream();
+
+ // 打印 Header Fields
+ /*Map> map = conn.getHeaderFields();
+ for (String str : map.keySet()) {
+ if (str != null) {
+ LogUtils.d(TAG, str + map.get(str));
+ }
+ }*/
+
+ // 设置文件名
+ //URL absUrl = conn.getURL();
+ String filename = "";
+
+ // 文件名生成方法 1
+ // 读取 Content-Disposition
+ try {
+ //LogUtils.d(TAG, "文件名生成方法 1");
+ // 通过Content-Disposition获取文件名,这点跟服务器有关,需要灵活变通
+ String szTemp = conn.getHeaderField("Content-Disposition");
+ if (szTemp != null) {
+ String szSearch = "filename=\"";
+ int nSearchStart = szTemp.indexOf(szSearch);
+ int nFileNameStart = nSearchStart + szSearch.length();
+ //LogUtils.d(TAG, "nFileNameStart : " + Integer.toString(nFileNameStart));
+ int nFileNameEnd = szTemp.indexOf("\"", nFileNameStart);
+ //LogUtils.d(TAG, "nFileNameEnd : " + Integer.toString(nFileNameEnd));
+ filename = szTemp.substring(nFileNameStart, nFileNameEnd);
+ }
+ } catch (Exception e) {
+ LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
+ }
+ //LogUtils.d(TAG, "filename is : " + filename);
+
+ // 文件名生成方法 2
+ // 读取 mszUrl
+ // 如果文件名包含"/"字符
+ // (例如 https://studio.zhangsken.cc/gitweb/libapputils.git/blob_plain/2aa898149aa7f0e34fe5bc11b2e70187f9f206bc:/libapputils/src/main/java/cc/winboll/studio/libapputils/LogView.java
+ // 的文件名方法1就得到"filename is : libapputils/src/main/java/cc/winboll/studio/libapputils/LogView.java")
+ // 或者为空就执行以下函数段
+ if ((filename.lastIndexOf("/") > 0)
+ || filename.trim().equals("")) {
+ //LogUtils.d(TAG, "文件名生成方法 2");
+ // 示例 https://studio.zhangsken.cc/gitweb/libaes.git/rss
+ // 截取最后的名称"rss"。
+ // 示例 https://studio.zhangsken.cc/gitweb/libaes.git/
+ // 最后是反斜杆结尾。就截取最后两个斜杆之间的名称"libaes.git"。
+ Pattern pattern = Pattern.compile(".*[/]([^/]+)[/]?$", Pattern.MULTILINE);
+ Matcher matcher = pattern.matcher(mszUrl);
+ if (matcher.find()) {
+ filename = matcher.replaceAll("$1");
+ }
+
+ //LogUtils.d(TAG, "转化文件名为可操作易读写格式");
+ //LogUtils.d(TAG, "filename is : " + filename);
+ // 转化文件名为可操作易读写格式
+ String filenameTemp = URLDecoder.decode(filename, "UTF-8");
+ //filenameTemp = filenameTemp.replace("/", "_");
+ //filename = filenameTemp.replace(".", "-");
+ filename = filenameTemp;
+
+ // 如果文件名没有".*"后缀,就加上".dat"后缀
+ if (filename.lastIndexOf(".") < 0) {
+ filename += ".dat";
+ }
+ }
+ //LogUtils.d(TAG, "filename is : " + filename);
+
+ // 如果指定了保存名称就用指定的名称
+ if (mszDowndloadName != null && !mszDowndloadName.equals("")) {
+ filename = mszDowndloadName;
+ }
+
+ mfDownloadFile = createSavedFile(filename);
+ // 如果文件存在就退出,没有就创建一个并下载
+ if (mfDownloadFile.exists()) {
+ LogUtils.d(TAG, "mfDownloadFile : " + mfDownloadFile.getPath());
+ fout = new FileOutputStream(mfDownloadFile);
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ fout.write(buffer, 0, len);
+ }
+ LogUtils.i(TAG, "DownLoad is well done : " + mfDownloadFile.getPath());
+ // 处理接收到的项目列表文件
+ handleDownloadFile();
+ }
+ } catch (Exception e) {
+ LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
+ }
+ }
+ if (fout != null) {
+ try {
+ fout.close();
+ } catch (IOException e) {
+ LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
+ }
+ }
+ }
+ }
+
+ //
+ //
+ File createSavedFile(String szOriginDownloadName) {
+ File fOriginDownload = new File(mfDownloadDir, szOriginDownloadName);
+ if (!fOriginDownload.exists()) {
+ // 如果该文件名的文件不存在,就表示可以保存为该文件名
+ // 预先创建新文件
+ try {
+ fOriginDownload.createNewFile();
+ } catch (IOException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ return fOriginDownload;
+ }
+
+ // 校验文件是否已存在,若存在就重命名新文件名
+ File fCheck = fOriginDownload;
+ int nAddName = 1;
+ while (fCheck.exists()) {
+ int nPoint = szOriginDownloadName.lastIndexOf(".");
+ if (nPoint > -1) {
+ LogUtils.d(TAG, "nPoint > -1");
+ fCheck = new File(mfDownloadDir, File.separator + szOriginDownloadName.substring(0, nPoint) + "(" + Integer.toString(nAddName) + ")." + szOriginDownloadName.substring(nPoint + 1));
+ } else {
+ LogUtils.d(TAG, "nPoint > -1 else");
+ fCheck = new File(mfDownloadDir, File.separator + szOriginDownloadName + "(" + Integer.toString(nAddName) + ")");
+ }
+ LogUtils.d(TAG, "nAddName++");
+ nAddName++;
+ }
+ // 预先创建新文件
+ try {
+ fCheck.createNewFile();
+ } catch (IOException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ return fCheck;
+ }
+
+ //
+ // 文件下载后的文件处理函数
+ //
+ abstract protected void handleDownloadFile();
+
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/BaseWebView.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/BaseWebView.java
new file mode 100644
index 0000000..43200de
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/BaseWebView.java
@@ -0,0 +1,499 @@
+package cc.winboll.studio.webpagesources.common;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/06/22 12:57:08
+ * @Describe 网页客户端视图基础类
+ * 参考:https://blog.csdn.net/Azhuoyanyan/article/details/17531887?app_version=5.15.2&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%2217531887%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app
+ *
+ */
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.DownloadListener;
+import android.webkit.HttpAuthHandler;
+import android.webkit.JavascriptInterface;
+import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebResourceError;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.GestureDetectorCompat;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.webpagesources.MainActivity;
+import cc.winboll.studio.webpagesources.R;
+import cc.winboll.studio.webpagesources.thread.LinkDownLoadThread;
+import cc.winboll.studio.webpagesources.util.UIUtil;
+import cc.winboll.studio.webpagesources.view.ItemLongClickedPopWindow;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BaseWebView extends WebView {
+
+ public static final String TAG = "BaseWebView";
+
+ public static final int WEB_CopyLink = 0;
+ public static final int WEB_DownLoadLink = 1;
+
+ protected Context mContext;
+ static String _mszLastUrl = "";
+ IOnPageFinished mIOnPageFinished;
+ protected MyWebChromeClient mMyWebChromeClient;
+ protected BaseWebViewClient mBaseWebViewClient;
+ protected BaseWebViewHandler mBaseWebViewHandler;
+ private int downX, downY;
+ private GestureDetectorCompat mGestureDetector;
+ ValueCallback mFilePathCallback;
+ WebChromeClient.FileChooserParams mFileChooserParams;
+
+ AuthLoginDialog mAuthLoginDialog;
+ // AuthenticationBean mLastAuthenticationBean;
+
+ /**
+ * 在java代码里new的时候会用到
+ * @param context
+ */
+ public BaseWebView(Context context) {
+ super(context);
+ }
+
+ /**
+ * 在xml布局文件中使用时自动调用
+ * @param context
+ */
+ public BaseWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+
+ mBaseWebViewHandler = new BaseWebViewHandler();
+ /*TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);
+ String text = ta.getString(R.styleable.test_text);
+ setText(text + " ZhanGSKen.CN");
+ ta.recycle();*/
+ //setBackgroundResource(R.drawable.custom_edittext_background);
+ //setMinWidth(100);
+ //setPadding(10,0,10,0);
+ //setPaddingRelative(0,0,0,0);
+ MyWebChromeClient mMyWebChromeClient = new MyWebChromeClient();
+ setWebChromeClient(mMyWebChromeClient);
+ mBaseWebViewClient = new BaseWebViewClient();
+ setWebViewClient(mBaseWebViewClient);
+
+ WebSettings mWebSettings = getSettings();
+ // 设置是否启用安全浏览。
+ mWebSettings.setSafeBrowsingEnabled(true);
+ // 启用JavaScript
+ mWebSettings.setJavaScriptEnabled(true);
+ // 设置可以支持缩放
+ mWebSettings.setSupportZoom(true);
+ // 设置true,才能让Webivew支持标签的viewport属性
+ mWebSettings.setUseWideViewPort(true);
+ // 设置出现缩放工具
+ mWebSettings.setBuiltInZoomControls(true);
+ // 设置隐藏缩放控件
+ mWebSettings.setDisplayZoomControls(false);
+ mWebSettings.setDefaultTextEncodingName("utf-8");
+ // 允许WebView运行JavaScript,并开启JavaScript Console以接收日志
+ mWebSettings.setDomStorageEnabled(true); // 如果有localStorage等需要
+ mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+ addJavascriptInterface(new JSConsole(mContext), "console");
+ addJavascriptInterface(new JS(mContext), "local_obj");
+
+ // 最小缩放等级
+ setInitialScale(60);
+
+ mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+ //winbollView.loadUrl("https://www.so.com");
+
+ mGestureDetector = new GestureDetectorCompat(mContext, new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public void onLongPress(MotionEvent e) {
+ downX = (int) e.getX();
+ downY = (int) e.getY();
+ //LogUtils.d(TAG, "downX is " + Integer.toString(downX));
+ //LogUtils.d(TAG, "downY is " + Integer.toString(downY));
+ }
+ });
+
+ setOnLongClickListener(new View.OnLongClickListener() {
+ String szUrl = "";
+ @Override
+ public boolean onLongClick(View v) {
+
+ WebView.HitTestResult result = ((WebView)v).getHitTestResult();
+ if (null == result)
+ return false;
+ int type = result.getType();
+ if (type == WebView.HitTestResult.UNKNOWN_TYPE)
+ return false;
+ if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
+ //let TextViewhandles context menu return true;
+ }
+ final ItemLongClickedPopWindow itemLongClickedPopWindow = new ItemLongClickedPopWindow(mContext, ItemLongClickedPopWindow.IMAGE_VIEW_POPUPWINDOW, UIUtil.dip2px(mContext, 180), ViewGroup.LayoutParams.WRAP_CONTENT);
+ // Setup custom handlingdepending on the type
+ switch (type) {
+ case WebView.HitTestResult.PHONE_TYPE: // 处理拨号
+ break;
+ case WebView.HitTestResult.EMAIL_TYPE: // 处理Email
+ break;
+ case WebView.HitTestResult.GEO_TYPE: // TODO
+ break;
+
+ case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
+ break;
+ case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超链接
+ // Log.d(DEG_TAG, "超链接");
+ //szUrl = result.getExtra();
+ //break;
+ case WebView.HitTestResult.IMAGE_TYPE: // 处理长按图片的菜单项
+ szUrl = result.getExtra();
+ //通过GestureDetector获取按下的位置,来定位PopWindow显示的位置
+ itemLongClickedPopWindow.showAtLocation(v, Gravity.TOP | Gravity.LEFT, downX, downY + 10);
+ break;
+ default:
+ break;
+ }
+
+ itemLongClickedPopWindow.getView(R.id.item_longclicked_copylink)
+ .setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 复制链接到剪贴板
+ // Gets a handle to the clipboard service.
+ ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+ // Creates a new text clip to put on the clipboard
+ ClipData clip = ClipData.newPlainText("simple text", szUrl);
+ // Set the clipboard's primary clip.
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
+ itemLongClickedPopWindow.dismiss();
+ }
+ });
+ itemLongClickedPopWindow.getView(R.id.item_longclicked_downloadlink)
+ .setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ itemLongClickedPopWindow.dismiss();
+ new Thread(new LinkDownLoadThread(szUrl)).start();
+ }
+ });
+ return true;
+ }
+ });
+ /*setOnCreateContextMenuListener(new View.OnCreateContextMenuListener(){
+ public void onCreateContextMenu(ContextMenu menu, View arg1,
+ ContextMenuInfo arg2) {
+ final HitTestResult hitTestResult = ((WebView) arg1).getHitTestResult();
+
+ MenuItem.OnMenuItemClickListener handler = new MenuItem.OnMenuItemClickListener() {
+
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case WEB_CopyLink : {
+
+ break;
+ }
+ case WEB_DownLoadLink : {
+ // 下载链接地址
+ new Thread(new LinkDownLoadThread(hitTestResult.getExtra())).start();
+ break;
+ }
+ }
+ return true;
+ }
+ };
+
+ // 添加上下文菜单
+ int resultType = hitTestResult.getType();
+ if ((resultType == HitTestResult.ANCHOR_TYPE) ||
+ (resultType == HitTestResult.IMAGE_ANCHOR_TYPE) ||
+ (resultType == HitTestResult.SRC_ANCHOR_TYPE) ||
+ (resultType == HitTestResult.SRC_IMAGE_ANCHOR_TYPE)) {
+ Intent i = new Intent();
+ //AppLog.d("extrea", result.getExtra());
+ MenuItem item = menu.add(0, WEB_CopyLink, 0, "Copy Link").setOnMenuItemClickListener(handler);
+ MenuItem item2 = menu.add(0, WEB_DownLoadLink, 0, "DownLoad Link").setOnMenuItemClickListener(handler);
+ item.setIntent(i);
+ //menu.setHeaderTitle(hitTestResult.getExtra());
+ TextView tv = new TextView(mContext);
+ tv.setText(hitTestResult.getExtra());
+ tv.setTextColor(0x8F000000);
+ tv.setPadding(15, 5, 15, 5);
+ menu.setHeaderView(tv);
+ }
+ }
+ });
+
+ setDownloadListener(new MyDownLoadListener(mContext));*/
+ }
+
+ /**
+ * 不会自动调用,如果有默认style时,在第二个构造函数中调用
+ * @param context
+ * @param attrs
+ * @param defStyleAttr
+ */
+ public BaseWebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /*public void setLastUrl(String lastUrl) {
+ this.mszLastUrl = lastUrl;
+ }*/
+
+ public String getLastUrl() {
+ return _mszLastUrl;
+ }
+
+ @Override
+ public void loadUrl(String url) {
+ Pattern patternHttp = Pattern.compile("(?i)^http[s]{0,1}://", Pattern.CASE_INSENSITIVE);
+ Matcher matcherHttp = patternHttp.matcher(url);
+ if (matcherHttp.matches()) {
+ // 加载普通 Http 协议访问
+ stopLoading();
+ loadUrl(url);
+ return;
+ } else {
+ // 加载其他协议类型访问
+ Pattern pattern = Pattern.compile("^[a-z]+:.*", Pattern.MULTILINE);
+ Matcher matcher = pattern.matcher(url);
+ if (matcher.find()) {
+ super.loadUrl(url);
+ return;
+ }
+ }
+ }
+
+ public void setFilePathCallback(ValueCallback mFilePathCallback) {
+ this.mFilePathCallback = mFilePathCallback;
+ }
+
+ public ValueCallback getFilePathCallback() {
+ return mFilePathCallback;
+ }
+
+ public void setFileChooserParams(WebChromeClient.FileChooserParams mFileChooserParams) {
+ this.mFileChooserParams = mFileChooserParams;
+ }
+
+ public WebChromeClient.FileChooserParams getFileChooserParams() {
+ return mFileChooserParams;
+ }
+
+ private class MyWebChromeClient extends WebChromeClient {
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ //super.onProgressChanged(view, newProgress);
+ }
+
+ @Override
+ public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
+ setFilePathCallback(filePathCallback);
+ setFileChooserParams(fileChooserParams);
+ //ToastUtils.show("onShowFileChooser");
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("*/*");
+ ((AppCompatActivity)mContext).startActivityForResult(intent, MainActivity.REQUEST_FILE_CHOOSER);
+ return true;
+ //return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ return super.onTouchEvent(event);
+ }
+
+ class BaseWebViewHandler extends Handler {
+
+ public static final String TAG = "BaseWebViewHandler";
+
+ public static final int MSG_RELOAD_LOG = 0X100;
+ public static final int MSG_SHOW_DATA = 0X123;
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_DATA:{
+ //LogUtils.d(TAG, "(String)msg.obj is " + (String)msg.obj);
+ loadDataWithBaseURL("", (String)msg.obj, "text/html", "UTF-8", "");
+ break;
+ }
+ }
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * 逻辑处理
+ * @author linzewu
+ */
+ final class InJavaScriptLocalObj {
+ @JavascriptInterface
+ public void getSource(String html) {
+ //LogUtils.d(TAG, html);
+ }
+ }
+
+ public class MyDownLoadListener implements DownloadListener {
+ private Context mContext;
+
+ public MyDownLoadListener(Context context) {
+ this.mContext = context;
+ }
+
+ @Override
+ public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
+ Uri uri = Uri.parse(url);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ mContext.startActivity(intent);
+ }
+ }
+
+ /**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/06/22 12:57:08
+ * @Describe 网页客户端基础类
+ */
+ class BaseWebViewClient extends WebViewClient {
+
+ public static final String TAG = "BaseWebViewClient";
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ view.loadUrl("javascript:window.local_obj.showSource(''+" +
+ "document.getElementsByTagName('html')[0].innerHTML+'');");
+ super.onPageFinished(view, url);
+ mIOnPageFinished.onPageFinished(url);
+ LogUtils.d(TAG, "Page load finished : " + url);
+ _mszLastUrl = url;
+ //ToastUtils.show("onPageFinished");
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
+ @Override
+ public void run() {
+ if (mAuthLoginDialog != null) {
+ mAuthLoginDialog.checkAndSaveLastAuthenticationBean();
+ mAuthLoginDialog = null;
+ }
+ }
+ }, 1000);
+
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ // WinBoll Studio 网站证书校验
+ /*if (SSLUtil.isSSLCertOk(error.getCertificate(), "9F568FCA9E2B3D59FF3BE2F5FF77CB5BFE1D30562D9880D2D354F0DF63A012E1")) {
+ LogUtils.d(TAG, "WinBoll Studio 网站证书校验通过。");
+ handler.proceed();
+ return;
+ }*/
+ LogUtils.d(TAG, "onReceivedSslError 0\nerror : " + error.toString());
+ //LogUtils.d(TAG, "On Received Ssl Error.");
+ //handler.proceed();
+
+ //super.onReceivedSslError(view, handler, error);
+ }
+
+ @Override
+ public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
+ int statusCode = errorResponse.getStatusCode();
+ /*if (statusCode == 404) {
+ // 处理404错误
+ Log.d("WebView", "Received a 404 error: " + failingUrl);
+ }*/
+ LogUtils.d(TAG, "onReceivedHttpError statusCode " + Integer.toString(statusCode));
+ super.onReceivedHttpError(view, request, errorResponse);
+ }
+
+ @Override
+ public void onReceivedError(WebView view, int errorCode,
+ String description, String failingUrl) {
+ super.onReceivedError(view, errorCode, description, failingUrl);
+ /*if (errorCode == WebViewClient.ERROR_TIMEOUT) {
+ LogUtils.d(TAG, "WebViewClient.ERROR_TIMEOUT");
+ ToastUtils.show("WebViewClient.ERROR_TIMEOUT");
+ } else if (errorCode == WebViewClient.ERROR_UNKNOWN) {
+ LogUtils.d(TAG, "WebViewClient.ERROR_UNKNOWN");
+ ToastUtils.show("WebViewClient.ERROR_UNKNOWN");
+ }*/
+ LogUtils.d(TAG, "onReceivedError 1\nerrorCode : " + errorCode
+ + "\ndescription :" + description + "\nfailingUrl : " + failingUrl);
+
+ //在此处显示加载失败页面
+ //loadFailure.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
+ super.onReceivedError(view, request, error);
+ LogUtils.d(TAG, "onReceivedError 2\nUrl : " + request.getUrl()
+ + "\nErrorCode : " + error.getErrorCode()
+ + "\nDescription : " + error.getDescription());
+ }
+
+ @Override
+ public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {
+ super.onReceivedLoginRequest(view, realm, account, args);
+ LogUtils.d(TAG, "onReceivedLoginRequest");
+ }
+
+ @Override
+ public void onLoadResource(WebView view, String url) {
+ super.onLoadResource(view, url);
+ //加载资源
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view,
+ HttpAuthHandler handler, String host, String realm) {
+ //super.onReceivedHttpAuthRequest(view, handler, host, realm);
+ mAuthLoginDialog = new AuthLoginDialog(view, handler, host, realm);
+ mAuthLoginDialog.show();
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ //开始加载页面
+ LogUtils.d(TAG, "Page load started : " + url);
+ _mszLastUrl = url;
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ //点击链接跳转,对网页中超链接按钮的响应。
+ //view.loadUrl(url);
+ return true;
+ }
+ }
+
+ public interface IOnPageFinished {
+ void onPageFinished(String url);
+ }
+
+ public void setOnPageFinished(IOnPageFinished iOnPageFinished) {
+ mIOnPageFinished = iOnPageFinished;
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/JS.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/JS.java
new file mode 100644
index 0000000..3af07f4
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/JS.java
@@ -0,0 +1,28 @@
+package cc.winboll.studio.webpagesources.common;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/07/13 13:54:13
+ * @Describe 获取网页内容的接口类
+ */
+import android.content.Context;
+import android.webkit.JavascriptInterface;
+import cc.winboll.studio.webpagesources.fragment.SourcesFragment;
+
+public final class JS {
+
+ public static final String TAG = "JS";
+
+ Context mContext;
+
+ public JS(Context context) {
+ mContext = context;
+ }
+
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public void showSource(String html) {
+ //LogUtils.d(TAG, "showSource");
+ SourcesFragment.sendSourcesUpdateMessage(html);
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/JSConsole.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/JSConsole.java
new file mode 100644
index 0000000..5857b25
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/common/JSConsole.java
@@ -0,0 +1,34 @@
+package cc.winboll.studio.webpagesources.common;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2024/08/23 02:15:03
+ * @Describe Javascript Console Log 接收类。
+ */
+import android.content.Context;
+import android.webkit.JavascriptInterface;
+import cc.winboll.studio.libappbase.LogUtils;
+
+public class JSConsole {
+
+ public static final String TAG = "JSConsole";
+
+ Context mContext;
+
+ public JSConsole(Context context) {
+ //LogUtils.d(TAG, "JSConsole(Context context)");
+ mContext = context;
+ }
+
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public void log(String tag, String message) {
+ LogUtils.i(TAG, "console.log(...)\n" + tag + " : " + message);
+ }
+
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public void log(String message) {
+ LogUtils.i(TAG, "console.log(...)\n" + message);
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/fragment/SourcesFragment.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/fragment/SourcesFragment.java
new file mode 100644
index 0000000..52b3356
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/fragment/SourcesFragment.java
@@ -0,0 +1,111 @@
+package cc.winboll.studio.webpagesources.fragment;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/07/04 13:45:57
+ * @Describe 网页源码视图 Fragment
+ */
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.Fragment;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.webpagesources.R;
+import cc.winboll.studio.webpagesources.handler.SourcesFragmentHandler;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+public class SourcesFragment extends Fragment {
+
+ public static final String TAG = "SourcesFragment";
+
+ View mView;
+ EditText mEditText;
+ String mszHtmlPath;
+ String mszHtmlFileName;
+ static SourcesFragmentHandler _mSourcesFragmentHandler;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ //return super.onCreateView(inflater, container, savedInstanceState);
+ mView = inflater.inflate(R.layout.fragment_sources, container, false);
+ mEditText = mView.findViewById(R.id.fragmentsourcesEditText1);
+
+ _mSourcesFragmentHandler = new SourcesFragmentHandler(this);
+ mszHtmlFileName = getString(R.string.app_name) + ".html";
+ mszHtmlPath = getActivity().getExternalFilesDir(TAG) + "/" + mszHtmlFileName;
+
+ return mView;
+ }
+
+ public void showCurrentWebPageHtml(String szHtml) {
+ /*File fHtml = new File(App._fTempHtmlPath);
+ StringBuffer sb = new StringBuffer();
+ try {
+ BufferedReader in = null;
+ in = new BufferedReader(new InputStreamReader(new FileInputStream(fHtml), "UTF-8"));
+ String line = "";
+ while ((line = in.readLine()) != null) {
+ sb.append(line);
+ sb.append("\n");
+ }
+ mEditText.setText(sb.toString());
+ } catch (IOException e) {
+ LogUtils.d(TAG, "IOException : " + e.getMessage());
+ }*/
+ mEditText.setText(szHtml);
+ }
+
+ public void shareHtml() {
+ saveHtml(mEditText.getText().toString());
+
+ Uri uri;
+ File file = new File(mszHtmlPath);
+ if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上
+ uri = FileProvider.getUriForFile(getActivity().getApplicationContext(), getActivity().getApplicationContext().getPackageName() + ".fileprovider", file);
+ } else {
+ uri = Uri.fromFile(file);
+ }
+ Intent shareIntent = new Intent("android.intent.action.VIEW");
+ shareIntent.setDataAndType(uri, "text/html");
+ if (Build.VERSION.SDK_INT >= 24) {
+ shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ startActivity(Intent.createChooser(shareIntent, mszHtmlFileName));
+ }
+
+ public static void sendSourcesUpdateMessage(String szHtml) {
+ if (_mSourcesFragmentHandler != null) {
+ Message message = _mSourcesFragmentHandler.obtainMessage(SourcesFragmentHandler.MSG_UPDATE);
+ Bundle bundle = new Bundle();
+ bundle.putString(SourcesFragmentHandler.BUNDLE_KEY_HTML, szHtml);
+ message.setData(bundle);
+ _mSourcesFragmentHandler.sendMessage(message);
+ }
+ }
+
+ void saveHtml(String szHtml) {
+ try {
+ File fTempHtml = new File(mszHtmlPath);
+ if (fTempHtml.exists()) {
+ fTempHtml.delete();
+ }
+ BufferedWriter out = null;
+ out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fTempHtml, false), "UTF-8"));
+ out.write(szHtml);
+ out.close();
+ } catch (IOException e) {
+ LogUtils.d(TAG, "IOException : " + e.getMessage());
+ }
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/fragment/WebFragment.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/fragment/WebFragment.java
new file mode 100644
index 0000000..37488f5
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/fragment/WebFragment.java
@@ -0,0 +1,129 @@
+package cc.winboll.studio.webpagesources.fragment;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/07/04 13:45:32
+ * @Describe 网页浏览视图 Fragment
+ */
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+import androidx.fragment.app.Fragment;
+import cc.winboll.studio.libaes.views.AButton;
+import cc.winboll.studio.webpagesources.R;
+import cc.winboll.studio.webpagesources.common.BaseWebView;
+import cc.winboll.studio.webpagesources.view.URLAddressView;
+import com.baoyz.widget.PullRefreshLayout;
+
+public class WebFragment extends Fragment {
+
+ public static final String TAG = "WebFragment";
+
+ View mView;
+ BaseWebView mBaseWebView;
+ URLAddressView mURLAddressView;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ //return super.onCreateView(inflater, container, savedInstanceState);
+ mView = inflater.inflate(R.layout.fragment_web, container, false);
+
+ mURLAddressView = mView.findViewById(R.id.fragmentwebURLAddressView1);
+ mURLAddressView.initAnchorView((LinearLayout)mView.findViewById(R.id.fragmentwebLinearLayout1));
+
+ AButton btnGoback = mView.findViewById(R.id.fragmentwebAButton1);
+ btnGoback.setOnClickListener(new AButton.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ mBaseWebView.goBack();
+ }
+ });
+ AButton btnGoForward = mView.findViewById(R.id.fragmentwebAButton2);
+ btnGoForward.setOnClickListener(new AButton.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ if (mURLAddressView.getURLAddressText().equals(mBaseWebView.getUrl())) {
+ mBaseWebView.goForward();
+ } else {
+ mBaseWebView.stopLoading();
+ mBaseWebView.loadUrl(mURLAddressView.getURLAddressText());
+ }
+
+ }
+ });
+ AButton btnReload = mView.findViewById(R.id.fragmentwebAButton3);
+ btnReload.setOnClickListener(new AButton.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ mBaseWebView.stopLoading();
+ mBaseWebView.clearCache(true);
+ mBaseWebView.reload();
+ }
+ });
+ AButton btnStopLoading = mView.findViewById(R.id.fragmentwebAButton4);
+ btnStopLoading.setOnClickListener(new AButton.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ mBaseWebView.stopLoading();
+ }
+ });
+
+ mBaseWebView = mView.findViewById(R.id.fragmentwebBaseWebView1);
+ mBaseWebView.setOnPageFinished(new BaseWebView.IOnPageFinished(){
+ @Override
+ public void onPageFinished(String url) {
+ mURLAddressView.setURLAddressText(url);
+ }
+ });
+
+ final PullRefreshLayout layout = mView.findViewById(R.id.fragmentwebPullRefreshLayout1);
+ // listen refresh event
+ layout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ // start refresh
+ mBaseWebView.stopLoading();
+ mBaseWebView.clearCache(true);
+ mBaseWebView.reload();
+ layout.setRefreshing(false);
+ }
+ });
+ return mView;
+ }
+
+ public BaseWebView getWebView() {
+ return mBaseWebView;
+ }
+
+ public void loadUrl(String szUrl) {
+ mBaseWebView.loadUrl(szUrl);
+ }
+
+ public void reloadLastUrl() {
+ mBaseWebView.stopLoading();
+ mBaseWebView.loadUrl(mBaseWebView.getLastUrl());
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mBaseWebView != null) {
+ mBaseWebView.destroy();
+
+ ViewParent parent = mBaseWebView.getParent();
+ if (parent != null) {
+ ((ViewGroup) parent).removeView(mBaseWebView);
+ }
+ mBaseWebView.stopLoading();
+ // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
+ mBaseWebView.getSettings().setJavaScriptEnabled(false);
+ mBaseWebView.clearHistory();
+ mBaseWebView.clearView();
+ mBaseWebView.removeAllViews();
+ mBaseWebView.destroy();
+ }
+ super.onDestroy();
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/handler/SourcesFragmentHandler.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/handler/SourcesFragmentHandler.java
new file mode 100644
index 0000000..8326a96
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/handler/SourcesFragmentHandler.java
@@ -0,0 +1,36 @@
+package cc.winboll.studio.webpagesources.handler;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/07/13 15:43:48
+ * @Describe 源码视图窗口事务处理类
+ */
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import cc.winboll.studio.webpagesources.fragment.SourcesFragment;
+
+public class SourcesFragmentHandler extends Handler {
+
+ public static final String TAG = "SourcesFragmentHandler";
+
+ public static final int MSG_UPDATE = 0;
+ public static final String BUNDLE_KEY_HTML = "BUNDLE_KEY_HTML";
+
+ SourcesFragment mSourcesFragment;
+ public SourcesFragmentHandler(SourcesFragment fragment) {
+ mSourcesFragment = fragment;
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE:{
+ Bundle bundle = msg.getData();
+ String szHtml = (String)bundle.getSerializable(BUNDLE_KEY_HTML);
+ mSourcesFragment.showCurrentWebPageHtml(szHtml);
+ break;
+ }
+ }
+ super.handleMessage(msg);
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/models/AuthenticationBean.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/models/AuthenticationBean.java
new file mode 100644
index 0000000..04c7ac0
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/models/AuthenticationBean.java
@@ -0,0 +1,125 @@
+package cc.winboll.studio.webpagesources.models;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2024/08/26 15:43:22
+ * @Describe 验证信息数据类
+ */
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import cc.winboll.studio.libappbase.BaseBean;
+import java.io.IOException;
+
+public class AuthenticationBean extends BaseBean {
+
+ public static final String TAG = "AuthenticationBean";
+
+ // 主机名
+ String host;
+ // 认证区域
+ String realm;
+ // 用户名
+ String userName;
+ // 密码
+ String password;
+
+ public AuthenticationBean(String host, String realm, String userName, String password) {
+ this.host = host;
+ this.realm = realm;
+ this.userName = userName;
+ this.password = password;
+ }
+
+ public AuthenticationBean(AuthenticationBean bean) {
+ this.host = bean.getHost();
+ this.realm = bean.getRealm();
+ this.userName = bean.getUserName();
+ this.password = bean.getPassword();
+ }
+
+ public AuthenticationBean() {
+ this.host = "";
+ this.realm = "";
+ this.userName = "";
+ this.password = "";
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getName() {
+ return AuthenticationBean.class.getName();
+ }
+
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ AuthenticationBean bean = this;
+ jsonWriter.name("host").value(bean.getHost());
+ jsonWriter.name("realm").value(bean.getRealm());
+ jsonWriter.name("userName").value(bean.getUserName());
+ jsonWriter.name("password").value(bean.getPassword());
+ }
+
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
+ if (name.equals("host")) {
+ setHost(jsonReader.nextString());
+ } else if (name.equals("realm")) {
+ setRealm(jsonReader.nextString());
+ } else if (name.equals("userName")) {
+ setUserName(jsonReader.nextString());
+ } else if (name.equals("password")) {
+ setPassword(jsonReader.nextString());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue();
+ }
+ }
+ // 结束 JSON 对象
+ jsonReader.endObject();
+ return this;
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/thread/LinkDownLoadThread.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/thread/LinkDownLoadThread.java
new file mode 100644
index 0000000..f223d8b
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/thread/LinkDownLoadThread.java
@@ -0,0 +1,25 @@
+package cc.winboll.studio.webpagesources.thread;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/06/22 21:19:59
+ * @Describe 链接下载进程类
+ */
+import android.os.Environment;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.webpagesources.common.BaseDownLoadThread;
+import java.io.File;
+
+public class LinkDownLoadThread extends BaseDownLoadThread {
+
+ public static final String TAG = "LinkDownLoadThread";
+
+ public LinkDownLoadThread(String szUrl) {
+ super(new File(Environment.getExternalStorageDirectory(), "Download"), szUrl);
+ }
+
+ @Override
+ protected void handleDownloadFile() {
+ LogUtils.d(TAG, "handleDownloadFile");
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/util/SSLUtil.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/util/SSLUtil.java
new file mode 100644
index 0000000..76c73a3
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/util/SSLUtil.java
@@ -0,0 +1,79 @@
+package cc.winboll.studio.webpagesources.util;
+
+/*
+ * 网站证书校验工具类
+ * Power By :
+ * ZhanGSKen@QQ.COM
+ * https://blog.csdn.net/
+ * https://blog.csdn.net/lsyz0021/article/details/54669914
+ *
+ */
+import android.net.http.SslCertificate;
+import android.os.Bundle;
+import android.util.Log;
+import java.io.ByteArrayInputStream;
+import java.security.MessageDigest;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public class SSLUtil {
+ public static final String TAG = "SSLUtil";
+
+ /**
+ * SSL证书错误,手动校验https证书
+ *
+ * @param cert https证书
+ * @param sha256Str sha256值
+ * @return true通过,false失败
+ */
+ public static boolean isSSLCertOk(SslCertificate cert, String sha256Str) {
+ byte[] SSLSHA256 = hexToBytes(sha256Str.toLowerCase());
+ Bundle bundle = SslCertificate.saveState(cert);
+ if (bundle != null) {
+ byte[] bytes = bundle.getByteArray("x509-certificate");
+ if (bytes != null) {
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Certificate ca = cf.generateCertificate(new ByteArrayInputStream(bytes));
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ byte[] key = sha256.digest(((X509Certificate) ca).getEncoded());
+ return Arrays.equals(key, SSLSHA256);
+ } catch (Exception e) {
+ Log.d(TAG, "Exception : " + e.getMessage());
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * hexString转byteArr
+ * 例如:
+ * hexString2Bytes("00A8") returns { 0, (byte) 0xA8 }
+ *
+ * @param hexString
+ * @return 字节数组
+ */
+ public static byte[] hexToBytes(String hexString) {
+
+ if (hexString == null || hexString.trim().length() == 0)
+ return null;
+
+ int length = hexString.length() / 2;
+ char[] hexChars = hexString.toCharArray();
+ byte[] bytes = new byte[length];
+ String hexDigits = "0123456789abcdef";
+ for (int i = 0; i < length; i++) {
+ int pos = i * 2; // 两个字符对应一个byte
+ int h = hexDigits.indexOf(hexChars[pos]) << 4; // 注1
+ int l = hexDigits.indexOf(hexChars[pos + 1]); // 注2
+ if (h == -1 || l == -1) { // 非16进制字符
+ return null;
+ }
+ bytes[i] = (byte) (h | l);
+ }
+ return bytes;
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/util/UIUtil.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/util/UIUtil.java
new file mode 100644
index 0000000..b2aa872
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/util/UIUtil.java
@@ -0,0 +1,18 @@
+package cc.winboll.studio.webpagesources.util;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/06/23 03:57:30
+ * @Describe 视图辅助工具类
+ */
+import android.content.Context;
+
+public class UIUtil {
+
+ public static final String TAG = "UIUtil";
+
+ public static int dip2px(Context context, float dpValue) {
+ float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/view/ItemLongClickedPopWindow.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/view/ItemLongClickedPopWindow.java
new file mode 100644
index 0000000..c191076
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/view/ItemLongClickedPopWindow.java
@@ -0,0 +1,86 @@
+package cc.winboll.studio.webpagesources.view;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/06/23 03:45:50
+ * @Describe 网页浏览长按弹出菜单类
+ */
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.PopupWindow;
+import cc.winboll.studio.webpagesources.R;
+
+public class ItemLongClickedPopWindow extends PopupWindow {
+ /**
+ * 书签条目弹出菜单 * @value * {@value} *
+ */
+ public static final int FAVORITES_ITEM_POPUPWINDOW = 0;
+ /**
+ * 书签页面弹出菜单 * @value * {@value} *
+ */
+ public static final int FAVORITES_VIEW_POPUPWINDOW = 1;
+ /**
+ * 历史条目弹出菜单 * @value * {@value} *
+ */
+ public static final int HISTORY_ITEM_POPUPWINDOW = 3;
+ /**
+ * 历史页面弹出菜单 * @value * {@value} *
+ */
+ public static final int HISTORY_VIEW_POPUPWINDOW = 4;
+ /**
+ * 图片项目弹出菜单 * @value * {@value} *
+ */
+ public static final int IMAGE_VIEW_POPUPWINDOW = 5;
+ /**
+ * 超链接项目弹出菜单 * @value * {@value} *
+ */
+ public static final int ACHOR_VIEW_POPUPWINDOW = 6;
+ private LayoutInflater itemLongClickedPopWindowInflater;
+ private View itemLongClickedPopWindowView;
+ private Context context;
+ private int type;
+
+ /**
+ * 构造函数 * @param context 上下文 * @param width 宽度 * @param height 高度 *
+ */
+ public ItemLongClickedPopWindow(Context context, int type, int width, int height) {
+ super(context);
+ this.context = context;
+ this.type = type;
+ //创建
+ this.initTab();
+ //设置默认选项
+ setWidth(width);
+ setHeight(height);
+ setContentView(this.itemLongClickedPopWindowView);
+ setOutsideTouchable(true);
+ setFocusable(true);
+ }
+
+ //实例化
+ private void initTab() {
+ this.itemLongClickedPopWindowInflater = LayoutInflater.from(this.context);
+ switch (type) {
+// case FAVORITES_ITEM_POPUPWINDOW:
+// this.itemLongClickedPopWindowView = this.itemLongClickedPopWindowInflater.inflate(R.layout.list_item_longclicked_favorites, null);
+// break;
+// case FAVORITES_VIEW_POPUPWINDOW: //对于书签内容弹出菜单,未作处理
+// break;
+// case HISTORY_ITEM_POPUPWINDOW:
+// this.itemLongClickedPopWindowView = this.itemLongClickedPopWindowInflater.inflate(R.layout.list_item_longclicked_history, null);
+// break;
+// case HISTORY_VIEW_POPUPWINDOW: //对于历史内容弹出菜单,未作处理
+// break;
+// case ACHOR_VIEW_POPUPWINDOW: //超链接
+// break;
+ case IMAGE_VIEW_POPUPWINDOW: //图片
+ this.itemLongClickedPopWindowView = this.itemLongClickedPopWindowInflater.inflate(R.layout.list_item_longclicked_img, null);
+ break;
+ }
+ }
+
+ public View getView(int id) {
+ return this.itemLongClickedPopWindowView.findViewById(id);
+ }
+}
diff --git a/webpagesources/src/main/java/cc/winboll/studio/webpagesources/view/URLAddressView.java b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/view/URLAddressView.java
new file mode 100644
index 0000000..0c15d19
--- /dev/null
+++ b/webpagesources/src/main/java/cc/winboll/studio/webpagesources/view/URLAddressView.java
@@ -0,0 +1,481 @@
+package cc.winboll.studio.webpagesources.view;
+
+/**
+ * @Author ZhanGSKen@QQ.COM
+ * @Date 2023/07/16 17:52:43
+ * @Describe 网页地址栏
+ */
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import androidx.appcompat.widget.ListPopupWindow;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.webpagesources.R;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+
+public class URLAddressView extends LinearLayout {
+
+ public static final String TAG = "URLEditText";
+
+ Context mContext;
+ EditText mURLAddressEditText;
+ ImageView mRightImageView;
+ ImageView mLeftImageView;
+ ListPopupWindow mListPopupWindow;
+ boolean mIsListPopupWindow_OnShow = false;
+ long mnRightImageViewTouchCount = 0;
+ String mszConfigPath;
+ ArrayList mlistURLInfo;
+ //Drawable drawable_r;
+ //Drawable drawable_l_no;
+ //Drawable drawable_l_yes;
+ LinearLayout mllAnchorView;
+
+ /**
+ * 在java代码里new的时候会用到
+ * @param context
+ */
+ public URLAddressView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ /**
+ * 在xml布局文件中使用时自动调用
+ * @param context
+ */
+ public URLAddressView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * 不会自动调用,如果有默认style时,在第二个构造函数中调用
+ * @param context
+ * @param attrs
+ * @param defStyleAttr
+ */
+ public URLAddressView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ /**
+ * 只有在API版本>21时才会用到
+ * 不会自动调用,如果有默认style时,在第二个构造函数中调用
+ * @param context
+ * @param attrs
+ * @param defStyleAttr
+ * @param defStyleRes
+ */
+ //@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public URLAddressView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ }
+
+ public void initAnchorView(LinearLayout llAnchorView) {
+ mllAnchorView = llAnchorView;
+ }
+
+ void init(Context context) {
+ mContext = context;
+ View viewMain = inflate(mContext, R.layout.view_urladdress, null);
+ mURLAddressEditText = viewMain.findViewById(R.id.viewurladdressEditText1);
+ mLeftImageView = viewMain.findViewById(R.id.viewurladdressImageView1);
+ mRightImageView = viewMain.findViewById(R.id.viewurladdressImageView2);
+ addView(viewMain);
+
+ mszConfigPath = context.getExternalFilesDir(TAG) + File.separator + "_mszConfigPath.json";
+ mlistURLInfo = new ArrayList();
+ mlistURLInfo = loadListURLInfo();
+
+ mURLAddressEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // 当文本发生改变时,这个方法会被调用
+ //LogUtils.d(TAG, "Text changed to: " + s.toString());
+ for (int i = 0; i < 0; i++) {
+ if (mURLAddressEditText.equals(mlistURLInfo.get(i).getUrl())) {
+ updateFavoriteButtonStatus();
+ return;
+ }
+ }
+
+ updateFavoriteButtonStatus();
+ /*if (mIOnTextChanged != null) {
+ if (text != null) {
+ mIOnTextChanged.onTextChanged(text.toString());
+ }
+ }*/
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+ });
+
+ mURLAddressEditText.setOnTouchListener(new View.OnTouchListener(){
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ //ToastUtils.show("mURLAddressEditText ACTION_DOWN");
+ if(!mIsListPopupWindow_OnShow) {
+ mnRightImageViewTouchCount = 0;
+ }
+ }
+ return false;
+ }
+ });
+
+ mLeftImageView.setOnTouchListener(new View.OnTouchListener(){
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ changeURLFavoriteStatus(mURLAddressEditText.getText().toString());
+ } else {
+ LogUtils.d(TAG, "MotionEvent : " + Integer.toString(motionEvent.getAction()));
+ }
+ return false;
+ }
+ });
+
+ mRightImageView.setOnTouchListener(new View.OnTouchListener(){
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mnRightImageViewTouchCount % 2 == 0) {
+ // 弹出网址列表
+ showListPopulWindow();
+ }
+ mnRightImageViewTouchCount++;
+ } else {
+ LogUtils.d(TAG, "MotionEvent : " + Integer.toString(motionEvent.getAction()));
+ }
+ return false;
+ }
+ });
+
+ /*TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);
+ String text = ta.getString(R.styleable.test_text);
+ setText(text + " ZhanGSKen.CN");
+ ta.recycle();*/
+ //drawable_r = getResources().getDrawable(R.drawable.ic_form_dropdown);
+ //drawable_n.setBounds(0, 0, drawable_n.getMinimumWidth(),drawable_n.getMinimumHeight()); //此为必须写的
+ //drawable_r.setBounds(0, 0, 80, 80); //此为必须写的
+
+ //drawable_l_no = getResources().getDrawable(R.drawable.ic_favorite_outline);
+ //drawable_n.setBounds(0, 0, drawable_n.getMinimumWidth(),drawable_n.getMinimumHeight()); //此为必须写的
+ //drawable_l_no.setBounds(0, 0, 80, 80); //此为必须写的
+
+ //drawable_l_yes = getResources().getDrawable(R.drawable.ic_favorite);
+ //drawable_n.setBounds(0, 0, drawable_n.getMinimumWidth(),drawable_n.getMinimumHeight()); //此为必须写的
+ //drawable_l_yes.setBounds(0, 0, 80, 80); //此为必须写的
+
+ //setFavoriteNo();
+ //setOnTouchListener(mOnTouchListener);
+ //setMinWidth(100);
+ //setPadding(10,0,10,0);
+ //setPaddingRelative(0,0,0,0);
+
+ }
+
+ public String getURLAddressText() {
+ return mURLAddressEditText.getText().toString();
+ }
+
+ public void setURLAddressText(String szUrl) {
+ mURLAddressEditText.setText(szUrl);
+ }
+
+ //
+ // 更新收藏按钮显示状态
+ //
+ void updateFavoriteButtonStatus() {
+ if (isFavoritedURL(mURLAddressEditText.getText().toString())) {
+ mLeftImageView.setBackgroundResource(R.drawable.ic_favorite);
+ //setCompoundDrawables(drawable_l_yes, null, drawable_r, null);
+ } else {
+ mLeftImageView.setBackgroundResource(R.drawable.ic_favorite_outline);
+ //setCompoundDrawables(drawable_l_no, null, drawable_r, null);
+ }
+
+ }
+
+ /*@Override
+ public void setOnTouchListener(View.OnTouchListener l) {
+ super.setOnTouchListener(l);
+ }*/
+
+ /*View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ final int DRAWABLE_LEFT = 0;
+ //final int DRAWABLE_TOP = 1;
+ final int DRAWABLE_RIGHT = 2;
+ //final int DRAWABLE_BOTTOM = 3;
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (event.getX() >= (getWidth() - getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
+ // 弹出网址列表
+ LogUtils.d(TAG, "mOnTouchListener 1");
+ showListPopulWindow();
+ return false;
+ } else if (event.getX() <= (getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width())) {
+ // 修改输入框网址收藏状态
+ LogUtils.d(TAG, "mOnTouchListener 2");
+ changeURLFavoriteStatus(getText().toString());
+ return false;
+ }
+ }
+ return false;
+ }
+ };*/
+
+ //
+ // 改变网址的收藏状态:
+ // @返回值:收藏状态
+ // 如果没收藏就设置收藏,收藏状态返回true;
+ // 如果收藏了就取消收藏,收藏状态返回false;
+ //
+ public boolean changeURLFavoriteStatus(String szUrl) {
+ if (szUrl == null || szUrl.equals("")) {
+ return false;
+ }
+
+ for (URLInfo item : mlistURLInfo) {
+ if (item.getUrl().equals(szUrl)) {
+ mlistURLInfo.remove(item);
+ saveListURLInfo();
+ updateFavoriteButtonStatus();
+ return false;
+ }
+ }
+
+ URLInfo newItem = new URLInfo(szUrl);
+ mlistURLInfo.add(newItem);
+ saveListURLInfo();
+ updateFavoriteButtonStatus();
+ return true;
+ }
+
+ //
+ // 读取网址的收藏状态
+ //
+ public boolean isFavoritedURL(String szUrl) {
+ if ((mlistURLInfo == null) || (szUrl == null) || szUrl.equals("")) {return false;}
+
+ for (URLInfo item : mlistURLInfo) {
+ if (item.getUrl().equals(szUrl)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //
+ // 前置网址在收藏列表里的位置
+ //
+ public boolean postposesURL(String szUrl) {
+ for (URLInfo item : mlistURLInfo) {
+ if (item.getUrl().equals(szUrl)) {
+ mlistURLInfo.add(0, new URLInfo(szUrl));
+ mlistURLInfo.remove(item);
+ saveListURLInfo();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //
+ // 加载收藏的网址列表
+ //
+ public ArrayList loadListURLInfo() {
+ File fJson = new File(mszConfigPath);
+ try {
+ ArrayList listTemp = readJsonStream(new FileInputStream(fJson));
+ if (listTemp != null && listTemp.size() > 0) {
+ mlistURLInfo.clear();
+ mlistURLInfo.addAll(listTemp);
+ } else {
+ mlistURLInfo.clear();
+ saveListURLInfo();
+ }
+ } catch (IOException e) {
+ LogUtils.d(TAG, "IOException : " + e.getMessage());
+ mlistURLInfo = new ArrayList();
+ saveListURLInfo();
+ }
+ return mlistURLInfo;
+ }
+
+ //
+ // 读取 Json 文件
+ //
+ ArrayList readJsonStream(InputStream in) throws IOException {
+ JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ return readURLInfoArrayList(reader);
+ }
+
+ //
+ // 读取 Json 文件的每一 Json 项
+ //
+ ArrayList readURLInfoArrayList(JsonReader reader) throws IOException {
+ ArrayList list = new ArrayList();
+ reader.beginArray();
+ while (reader.hasNext()) {
+ list.add(readURLInfo(reader));
+ }
+ reader.endArray();
+ return list;
+ }
+
+ //
+ // 读取 Json 文件的某一 Json 项
+ //
+ URLInfo readURLInfo(JsonReader reader) throws IOException {
+ URLInfo urlInfo = new URLInfo();
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (name.equals("url")) {
+ urlInfo.setUrl(reader.nextString());
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ return urlInfo;
+ }
+
+ //
+ // 写入 Json 文件的某一 Json 项
+ //
+ static void writeConfigs(JsonWriter writer, URLInfo urlInfo) throws IOException {
+ writer.beginObject();
+ writer.name("url").value(urlInfo.getUrl());
+ writer.endObject();
+ }
+
+ //
+ // 保存收藏的网址列表
+ //
+ void saveListURLInfo() {
+ try {
+ File fJson = new File(mszConfigPath);
+ writeJsonStream(new FileOutputStream(fJson, false), mlistURLInfo);
+ } catch (IOException e) {
+ LogUtils.d(TAG, "IOException : " + e.getMessage());
+ }
+ }
+
+ //
+ // 写入 Json 文件
+ //
+ void writeJsonStream(OutputStream out, ArrayList listURLInfo) throws IOException {
+ JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ writer.setIndent(" ");
+ writeConfigsArray(writer, listURLInfo);
+ writer.close();
+ }
+
+ //
+ // 记录 Json 文件的某一 Json 项
+ //
+ void writeConfigsArray(JsonWriter writer, ArrayList listURLInfo) throws IOException {
+ writer.beginArray();
+ for (URLInfo urlInfo : listURLInfo) {
+ writeConfigs(writer, urlInfo);
+ }
+ writer.endArray();
+ }
+
+ public class URLInfo {
+ String url;
+
+ public URLInfo() {}
+ public URLInfo(String url) {
+ this.url = url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+ }
+
+ //
+ // 弹出网址下拉框
+ // @ llAnchorView : 设置下拉列表基准控件
+ //
+ void showListPopulWindow() {
+ //final String[] list = {"asg","hshsh"};
+ int nListSize = mlistURLInfo.size();
+ final String[] list = new String[nListSize];
+ for (int i = 0; i < nListSize; i++) {
+ list[i] = mlistURLInfo.get(i).getUrl();
+ }
+ //final String[] list = (String[])mSetStringFavorite.toArray(new String[0]);
+
+
+ mListPopupWindow = new ListPopupWindow(mContext);
+ mListPopupWindow.setWidth(LayoutParams.WRAP_CONTENT);
+ mListPopupWindow.setHeight(LayoutParams.WRAP_CONTENT);
+ //listPopupWindow.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, list));//用android内置布局,或设计自己的样式
+ mListPopupWindow.setAdapter(new ArrayAdapter(mContext, R.layout.view_popurllist, R.id.viewpopurllistTextView1, list));//用android内置布局,或设计自己的样式
+ //设置下拉列表基准控件
+ //LinearLayout llAnchorView = findViewById(R.id.activitytesturlLinearLayout2);
+ mListPopupWindow.setAnchorView(mllAnchorView);
+ mListPopupWindow.setModal(false);
+ // 透明度
+ //listPopupWindow.setBackgroundDrawable(new ColorDrawable(0xEEAAAAAA));
+ mListPopupWindow.setBackgroundDrawable(mContext.getDrawable(R.drawable.bg_shadow));
+
+ mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {//设置项点击监听
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+ mURLAddressEditText.setText(list[i]);//展示选择的内容
+ postposesURL(list[i]);
+ updateFavoriteButtonStatus();
+ //setWebViewURL(list[i]);
+ mListPopupWindow.dismiss();//如果已经选择了,隐藏起来
+ mnRightImageViewTouchCount = 0;
+ }
+ });
+ mListPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener(){
+ @Override
+ public void onDismiss() {
+ mIsListPopupWindow_OnShow = false;
+ }
+ });
+ mIsListPopupWindow_OnShow = true;
+ mListPopupWindow.show();//下拉列表展示出来
+ }
+}
diff --git a/webpagesources/src/main/res/drawable/bg_shadow.xml b/webpagesources/src/main/res/drawable/bg_shadow.xml
new file mode 100644
index 0000000..6d3d898
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/bg_shadow.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_code_block_html.xml b/webpagesources/src/main/res/drawable/ic_code_block_html.xml
new file mode 100644
index 0000000..7305d42
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_code_block_html.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_favorite.xml b/webpagesources/src/main/res/drawable/ic_favorite.xml
new file mode 100644
index 0000000..a65f109
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_favorite.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_favorite_outline.xml b/webpagesources/src/main/res/drawable/ic_favorite_outline.xml
new file mode 100644
index 0000000..05a08c4
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_favorite_outline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_file_edit_outline.xml b/webpagesources/src/main/res/drawable/ic_file_edit_outline.xml
new file mode 100644
index 0000000..946a79e
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_file_edit_outline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_form_dropdown.xml b/webpagesources/src/main/res/drawable/ic_form_dropdown.xml
new file mode 100644
index 0000000..518f861
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_form_dropdown.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_launcher.xml b/webpagesources/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000..d4d1eaf
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_launcher_background.xml b/webpagesources/src/main/res/drawable/ic_launcher_background.xml
index d5fccc5..e6fad7e 100644
--- a/webpagesources/src/main/res/drawable/ic_launcher_background.xml
+++ b/webpagesources/src/main/res/drawable/ic_launcher_background.xml
@@ -5,7 +5,7 @@
android:viewportHeight="108"
android:viewportWidth="108">
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_launcher_foreground_web_white.xml b/webpagesources/src/main/res/drawable/ic_launcher_foreground_web_white.xml
new file mode 100644
index 0000000..801c235
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_launcher_foreground_web_white.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_web.xml b/webpagesources/src/main/res/drawable/ic_web.xml
new file mode 100644
index 0000000..d2d3ce9
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_web.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/ic_web_white.xml b/webpagesources/src/main/res/drawable/ic_web_white.xml
new file mode 100644
index 0000000..6051b07
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/ic_web_white.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/webpagesources/src/main/res/drawable/shape_gradient.xml b/webpagesources/src/main/res/drawable/shape_gradient.xml
new file mode 100644
index 0000000..c164fe9
--- /dev/null
+++ b/webpagesources/src/main/res/drawable/shape_gradient.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/activity_about.xml b/webpagesources/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..c23a2cf
--- /dev/null
+++ b/webpagesources/src/main/res/layout/activity_about.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/activity_main.xml b/webpagesources/src/main/res/layout/activity_main.xml
index de5ac09..06f328e 100644
--- a/webpagesources/src/main/res/layout/activity_main.xml
+++ b/webpagesources/src/main/res/layout/activity_main.xml
@@ -6,39 +6,35 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
+
-
+
-
+
-
+
-
+
+ android:layout_height="300dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/fragment_sources.xml b/webpagesources/src/main/res/layout/fragment_sources.xml
new file mode 100644
index 0000000..92f2b9a
--- /dev/null
+++ b/webpagesources/src/main/res/layout/fragment_sources.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/fragment_web.xml b/webpagesources/src/main/res/layout/fragment_web.xml
new file mode 100644
index 0000000..a1c41f7
--- /dev/null
+++ b/webpagesources/src/main/res/layout/fragment_web.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/list_item_longclicked_img.xml b/webpagesources/src/main/res/layout/list_item_longclicked_img.xml
new file mode 100644
index 0000000..9a6031e
--- /dev/null
+++ b/webpagesources/src/main/res/layout/list_item_longclicked_img.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/listview_authinfo.xml b/webpagesources/src/main/res/layout/listview_authinfo.xml
new file mode 100644
index 0000000..fc1faad
--- /dev/null
+++ b/webpagesources/src/main/res/layout/listview_authinfo.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/view_popurllist.xml b/webpagesources/src/main/res/layout/view_popurllist.xml
new file mode 100644
index 0000000..add6035
--- /dev/null
+++ b/webpagesources/src/main/res/layout/view_popurllist.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/view_toast.xml b/webpagesources/src/main/res/layout/view_toast.xml
new file mode 100644
index 0000000..d6a9915
--- /dev/null
+++ b/webpagesources/src/main/res/layout/view_toast.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/layout/view_urladdress.xml b/webpagesources/src/main/res/layout/view_urladdress.xml
new file mode 100644
index 0000000..e7f5abb
--- /dev/null
+++ b/webpagesources/src/main/res/layout/view_urladdress.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/menu/toolbar_about.xml b/webpagesources/src/main/res/menu/toolbar_about.xml
new file mode 100644
index 0000000..3673540
--- /dev/null
+++ b/webpagesources/src/main/res/menu/toolbar_about.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/webpagesources/src/main/res/menu/toolbar_authinfo.xml b/webpagesources/src/main/res/menu/toolbar_authinfo.xml
new file mode 100644
index 0000000..16ab736
--- /dev/null
+++ b/webpagesources/src/main/res/menu/toolbar_authinfo.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/webpagesources/src/main/res/menu/toolbar_main.xml b/webpagesources/src/main/res/menu/toolbar_main.xml
new file mode 100644
index 0000000..f964eb5
--- /dev/null
+++ b/webpagesources/src/main/res/menu/toolbar_main.xml
@@ -0,0 +1,25 @@
+
+
diff --git a/webpagesources/src/main/res/values/attrs.xml b/webpagesources/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..48ad1aa
--- /dev/null
+++ b/webpagesources/src/main/res/values/attrs.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/webpagesources/src/main/res/values/colors.xml b/webpagesources/src/main/res/values/colors.xml
index 479769a..d33d4a0 100644
--- a/webpagesources/src/main/res/values/colors.xml
+++ b/webpagesources/src/main/res/values/colors.xml
@@ -1,6 +1,15 @@
- #009688
- #00796B
- #FF9800
-
\ No newline at end of file
+
+ #FF000000
+ #FF000000
+
+ #FF196ABC
+ #FF002B57
+ #FF80BFFF
+ #FFA9A9A9
+ #FF000000
+ #FFFFFFFF
+
diff --git a/webpagesources/src/main/res/values/strings.xml b/webpagesources/src/main/res/values/strings.xml
index ab2779c..5d8fd20 100644
--- a/webpagesources/src/main/res/values/strings.xml
+++ b/webpagesources/src/main/res/values/strings.xml
@@ -1,4 +1,5 @@
WebPageSources
+ https://winboll.cc/
diff --git a/webpagesources/src/main/res/values/styles.xml b/webpagesources/src/main/res/values/styles.xml
index a70e242..b6d5bb4 100644
--- a/webpagesources/src/main/res/values/styles.xml
+++ b/webpagesources/src/main/res/values/styles.xml
@@ -7,5 +7,29 @@
- @color/colorPrimaryDark
- @color/colorAccent
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/xml/file_provider.xml b/webpagesources/src/main/res/xml/file_provider.xml
new file mode 100644
index 0000000..a53d7e6
--- /dev/null
+++ b/webpagesources/src/main/res/xml/file_provider.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webpagesources/src/main/res/xml/network_security_config.xml b/webpagesources/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..99592fd
--- /dev/null
+++ b/webpagesources/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ winboll.cc
+
+
+
+
+ 10.8.0.*
+
+
+
+
+ 192.168.*.*
+
+