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.*.* + +