diff --git a/.gitignore b/.gitignore index 0842589..1b281bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,6 @@ -/app/build/tmp -/app/build/outputs/logs -/app/build/outputs *.iml .gradle /local.properties -/.idea/workspace.xml -/.idea/libraries .DS_Store /build -/captures /.idea -app/src/test/ -daogenerator/libs/ -app-release.apk -/app/src/main/java/me/wizos/loread/dump/ -dump/ -/swipelayout/ -src/main/java/me/wizos/test/Test.java -/daogenerator/src/main/java/me/wizos/test/Test.java -/gradlew -/gradlew.bat diff --git a/README.md b/README.md index d456daf..08ca62c 100644 --- a/README.md +++ b/README.md @@ -9,31 +9,34 @@ # 简介 -Inoreader 是当下体验最好的国外 RSS 服务。利用其开放的 api ,完成了该第三方客户端。 +RSS 第三方客户端,支持 Inoreader、Feedly、TinyTinyRSS。 下载地址:[http://www.coolapk.com/apk/168423](http://www.coolapk.com/apk/168423) -# 布局&功能 +# 截图 ![截图](doc/overview.png) -如上图,从左至右依次对应 tab 栏的“设置,加星文章,文件夹/标签,已读未读文章” +如上图,从左至右依次为“登录、首页、文章页、分类、快速设置、设置” + +# 功能 目前实现以下几个功能: -- [x] 处理 文章状态(已读/未读/加星) -- [x] 文件夹与文件状态的交叉查询 -- [x] 设置 文章的保存期,及其过期后的清理 -- [x] 保存 离线状态下的一些网络请求(文章状态处理,图片下载),待有网再同步 +- [x] 黑夜主题 +- [x] 获取全文:支持根据规则或智能识别全文 +- [x] 保存近期文章的阅读进度 +- [x] 左右切换文章 +- [x] 自动清理过期文章 +- [x] 不同状态下(未读/加星/全部),各分组内文章的数量 +- [x] ~~保存 离线状态下的一些网络请求(文章状态处理,图片下载),待有网再同步~~ 对文章列表项的手势操作: - [x] 左滑是切换文章的“已读/未读”状态 - [x] 右滑是切换文章的“加星/取消加星”状态 - [x] 长按是“上面的文章标记为已读,下面的文章标记为已读” -- [x] 被置为已读的文章 textColor 会暗淡 -- [x] 手动对文章标记了状态的会在文章左下角显示一个状态 icon PS: @@ -41,17 +44,28 @@ PS: # 后期规划 - -在解决一些离奇 bug (如果有)的前提下,应该会添加以下一些功能: - -- [x] 保存最近文章的阅读进度 -- [x] 未读数根据当前分组实时变化 -- [x] webview占位图(点击下载图片) -- [x] 黑夜主题 -- [x] 被中断程序时的数据保存工作 -- [x] 文章内容页左右切换文章 +### Bug +- [ ] 优化反色算法,解决灰反色问题 +- [ ] 优化音频莫名暂停问题 +- [ ] 优化 ROOM 库带来的问题 + +### 功能 +- [ ] 优化朗读、播放音乐的界面 +- [ ] 支持全文搜索 +- [ ] 支持本地 RSS +- [ ] 支持获取不支持 RSS 站点的文章 +- [ ] 支持更换主题 +- [ ] 支持设置排版:字体、字号、字距、行距、背景色 +- [ ] 支持长按视频,图片,iframe候展示菜单 +- [ ] 本地训练机器学习模型,判断文章喜好 +- [ ] 检查添加的订阅地址是否有相似的订阅 + +### 技术 +- [ ] 优化代码结构,拆成不同模块 +- [ ] 改用最新的技术,例如 Kotlin +- [ ] 使用 CI 自动构建 APK 包 # 库的使用 -* OkHttp , Gson , Greendao , Glide 等等 +* OkHttp, Gson, ROOM, Glide 等等 diff --git a/agentweb-core/.gitignore b/agentweb-core/.gitignore new file mode 100644 index 0000000..423e3dc --- /dev/null +++ b/agentweb-core/.gitignore @@ -0,0 +1,3 @@ +/build +/src/androidTest/ +/src/test/ diff --git a/agentweb-core/build.gradle b/agentweb-core/build.gradle index d7b8a8f..a825343 100644 --- a/agentweb-core/build.gradle +++ b/agentweb-core/build.gradle @@ -1,17 +1,17 @@ apply plugin: 'com.android.library' -//apply plugin: 'maven' -//apply plugin: 'com.novoda.bintray-release' android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.3' defaultConfig { minSdkVersion 22 - targetSdkVersion 26 + targetSdkVersion 29 versionCode 1 versionName "1.0" -// testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } buildTypes { release { @@ -30,22 +30,15 @@ android { // defaultPublishConfig "debug" } - -task javadoc(type: Javadoc) { - options.encoding = "utf-8" -} dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - testImplementation 'junit:junit:4.12' - implementation 'com.android.support:support-v4:27.1.1' - implementation 'com.android.support:design:27.1.1' - compileOnly fileTree(include: ['*.jar'], dir: 'providedLibs') + androidTestImplementation('androidx.test.espresso:espresso-core:3.2.0', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testImplementation 'junit:junit:4.13' + implementation 'com.download.library:Downloader:4.1.2' + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation fileTree(include: ['*.jar'], dir: 'providedLibs') } -//publish { -// userOrg = 'just' -// groupId = 'com.just.agentweb' -// artifactId = 'agentweb' -// version = '4.0.2' -// description = 'very easy to build you web page' -// website = "https://github.com/Justson/AgentWeb" -//} + diff --git a/agentweb-core/providedLibs/alipaySdk-20180601.jar b/agentweb-core/providedLibs/alipaySdk-20180601.jar new file mode 100644 index 0000000..c133456 Binary files /dev/null and b/agentweb-core/providedLibs/alipaySdk-20180601.jar differ diff --git a/agentweb-core/src/main/AndroidManifest.xml b/agentweb-core/src/main/AndroidManifest.xml index 30588d4..823c772 100644 --- a/agentweb-core/src/main/AndroidManifest.xml +++ b/agentweb-core/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + package="com.just.agentweb" + > + android:resource="@xml/web_files_public"/> = Build.VERSION_CODES.LOLLIPOP) { - //适配5.0不允许http和https混合使用情况 - mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - // 设置字体缩放比例 -// mWebSettings.setTextZoom(100); - mWebSettings.setDatabaseEnabled(true); - mWebSettings.setAppCacheEnabled(true); - mWebSettings.setLoadsImagesAutomatically(true); - mWebSettings.setSupportMultipleWindows(false); - // 是否阻塞加载网络图片 协议http or https - mWebSettings.setBlockNetworkImage(false); - // 允许加载本地文件html file协议 - mWebSettings.setAllowFileAccess(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭 - mWebSettings.setAllowFileAccessFromFileURLs(false); - // 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源 - mWebSettings.setAllowUniversalAccessFromFileURLs(false); - } - mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true); - - // NARROW_COLUMNS 适应内容大小 , SINGLE_COLUMN 自适应屏幕 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - - mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); - } else { - mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); - } - // 缩放至屏幕的大小,如果webview内容宽度大于显示区域的宽度,那么将内容缩小,以适应显示区域的宽度, 默认是false - mWebSettings.setLoadWithOverviewMode(true); - mWebSettings.setUseWideViewPort(false); - mWebSettings.setDomStorageEnabled(true); - mWebSettings.setNeedInitialFocus(true); - mWebSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 - mWebSettings.setDefaultFontSize(16); - mWebSettings.setMinimumFontSize(12);//设置 WebView 支持的最小字体大小,默认为 8 - mWebSettings.setGeolocationEnabled(true); - // - String dir = AgentWebConfig.getCachePath(webView.getContext()); - - LogUtils.i(TAG, "dir:" + dir + " appcache:" + AgentWebConfig.getCachePath(webView.getContext())); - //设置数据库路径 api19 已经废弃,这里只针对 webkit 起作用 - mWebSettings.setGeolocationDatabasePath(dir); - mWebSettings.setDatabasePath(dir); - mWebSettings.setAppCachePath(dir); - - //缓存文件最大值 - mWebSettings.setAppCacheMaxSize(Long.MAX_VALUE); - - mWebSettings.setUserAgentString(getWebSettings() - .getUserAgentString() - .concat(USERAGENT_AGENTWEB) - .concat(USERAGENT_UC) - ); - - - LogUtils.i(TAG, "UserAgentString : " + mWebSettings.getUserAgentString()); - - - } - - @Override - public WebSettings getWebSettings() { - return mWebSettings; - } - - @Override - public WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient) { - webview.setWebChromeClient(webChromeClient); - - return this; - } - - @Override - public WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient) { - webView.setWebViewClient(webViewClient); - return this; - } - - @Override - public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) { - webView.setDownloadListener(downloadListener); - return this; - } - + private WebSettings mWebSettings; + private static final String TAG = AbsAgentWebSettings.class.getSimpleName(); + public static final String USERAGENT_UC = " UCBrowser/11.6.4.950 "; + public static final String USERAGENT_QQ_BROWSER = " MQQBrowser/8.0 "; + public static final String USERAGENT_AGENTWEB = AgentWebConfig.AGENTWEB_VERSION; + protected AgentWeb mAgentWeb; + + public static AbsAgentWebSettings getInstance() { + return new AgentWebSettingsImpl(); + } + + public AbsAgentWebSettings() { + } + + final void bindAgentWeb(AgentWeb agentWeb) { + this.mAgentWeb = agentWeb; + this.bindAgentWebSupport(agentWeb); + } + + protected abstract void bindAgentWebSupport(AgentWeb agentWeb); + + @Override + public IAgentWebSettings toSetting(WebView webView) { + settings(webView); + return this; + } + + private void settings(WebView webView) { + mWebSettings = webView.getSettings(); + mWebSettings.setJavaScriptEnabled(true); + mWebSettings.setSupportZoom(true); + mWebSettings.setBuiltInZoomControls(false); + mWebSettings.setSavePassword(false); + if (AgentWebUtils.checkNetwork(webView.getContext())) { + //根据cache-control获取数据。 + mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT); + } else { + //没网,则从本地获取,即离线加载 + mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + //适配5.0不允许http和https混合使用情况 + mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + mWebSettings.setTextZoom(100); + mWebSettings.setDatabaseEnabled(true); + mWebSettings.setAppCacheEnabled(true); + mWebSettings.setLoadsImagesAutomatically(true); + mWebSettings.setSupportMultipleWindows(false); + // 是否阻塞加载网络图片 协议http or https + mWebSettings.setBlockNetworkImage(false); + // 允许加载本地文件html file协议 + mWebSettings.setAllowFileAccess(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + // 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭 + mWebSettings.setAllowFileAccessFromFileURLs(false); + // 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源 + mWebSettings.setAllowUniversalAccessFromFileURLs(false); + } + mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + + mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); + } else { + mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); + } + mWebSettings.setLoadWithOverviewMode(false); + mWebSettings.setUseWideViewPort(false); + mWebSettings.setDomStorageEnabled(true); + mWebSettings.setNeedInitialFocus(true); + mWebSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 + mWebSettings.setDefaultFontSize(16); + mWebSettings.setMinimumFontSize(12);//设置 WebView 支持的最小字体大小,默认为 8 + mWebSettings.setGeolocationEnabled(true); + String dir = AgentWebConfig.getCachePath(webView.getContext()); + LogUtils.i(TAG, "dir:" + dir + " appcache:" + AgentWebConfig.getCachePath(webView.getContext())); + //设置数据库路径 api19 已经废弃,这里只针对 webkit 起作用 + mWebSettings.setGeolocationDatabasePath(dir); + mWebSettings.setDatabasePath(dir); + mWebSettings.setAppCachePath(dir); + //缓存文件最大值 + mWebSettings.setAppCacheMaxSize(Long.MAX_VALUE); + mWebSettings.setUserAgentString(getWebSettings() + .getUserAgentString() + .concat(USERAGENT_AGENTWEB) + .concat(USERAGENT_UC) + ); + LogUtils.i(TAG, "UserAgentString : " + mWebSettings.getUserAgentString()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // 安卓9.0后不允许多进程使用同一个数据目录,需设置前缀来区分 + // 参阅 https://blog.csdn.net/lvshuchangyin/article/details/89446629 + Context context = webView.getContext(); + String processName = ProcessUtils.getCurrentProcessName(context); + if (!context.getApplicationContext().getPackageName().equals(processName)) { + WebView.setDataDirectorySuffix(processName); + } + } + } + + @Override + public WebSettings getWebSettings() { + return mWebSettings; + } + + @Override + public WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient) { + webview.setWebChromeClient(webChromeClient); + return this; + } + + @Override + public WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient) { + webView.setWebViewClient(webViewClient); + return this; + } + + @Override + public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) { + webView.setDownloadListener(downloadListener); + return this; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebUIController.java b/agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebUIController.java index 14a9c7f..dc36312 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebUIController.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebUIController.java @@ -32,149 +32,148 @@ */ public abstract class AbsAgentWebUIController { - public static boolean HAS_DESIGN_LIB = false; - private Activity mActivity; - private WebParentLayout mWebParentLayout; - private volatile boolean mIsBindWebParent = false; - protected AbsAgentWebUIController mAgentWebUIControllerDelegate; - protected String TAG = this.getClass().getSimpleName(); - - static { - try { - Class.forName("android.support.design.widget.Snackbar"); - Class.forName("android.support.design.widget.BottomSheetDialog"); - HAS_DESIGN_LIB = true; - } catch (Throwable ignore) { - HAS_DESIGN_LIB = false; - } - } - - - protected AbsAgentWebUIController create() { - return HAS_DESIGN_LIB ? new DefaultDesignUIController() : new DefaultUIController(); - } - - protected AbsAgentWebUIController getDelegate() { - AbsAgentWebUIController mAgentWebUIController = this.mAgentWebUIControllerDelegate; - if (mAgentWebUIController == null) { - this.mAgentWebUIControllerDelegate = mAgentWebUIController = create(); - } - return mAgentWebUIController; - } - - final synchronized void bindWebParent(WebParentLayout webParentLayout, Activity activity) { - if (!mIsBindWebParent) { - mIsBindWebParent = true; - this.mWebParentLayout = webParentLayout; - this.mActivity = activity; - bindSupportWebParent(webParentLayout, activity); - } - } - - protected void toDismissDialog(Dialog dialog) { - if (dialog != null && dialog.isShowing()) { - dialog.dismiss(); - } - } - - protected void toShowDialog(Dialog dialog) { - if (dialog != null && !dialog.isShowing()) { - dialog.show(); - } - } - - protected abstract void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity); - - /** - * WebChromeClient#onJsAlert - * - * @param view - * @param url - * @param message - */ - public abstract void onJsAlert(WebView view, String url, String message); - - /** - * 咨询用户是否前往其他页面 - * - * @param view - * @param url - * @param callback - */ - public abstract void onOpenPagePrompt(WebView view, String url, Handler.Callback callback); - - /** - * WebChromeClient#onJsConfirm - * - * @param view - * @param url - * @param message - * @param jsResult - */ - public abstract void onJsConfirm(WebView view, String url, String message, JsResult jsResult); - - public abstract void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback); - - /** - * 强制下载弹窗 - * - * @param url 当前下载地址。 - * @param callback 用户操作回调回调 - */ - public abstract void onForceDownloadAlert(String url, Handler.Callback callback); - - /** - * WebChromeClient#onJsPrompt - * - * @param view - * @param url - * @param message - * @param defaultValue - * @param jsPromptResult - */ - public abstract void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult); - - /** - * 显示错误页 - * - * @param view - * @param errorCode - * @param description - * @param failingUrl - */ - public abstract void onMainFrameError(WebView view, int errorCode, String description, String failingUrl); - - /** - * 隐藏错误页 - */ - public abstract void onShowMainFrame(); - - /** - * 弹窗正在加载... - * - * @param msg - */ - public abstract void onLoading(String msg); - - /** - * 正在加载弹窗取消 - */ - public abstract void onCancelLoading(); - - /** - * @param message 消息 - * @param intent 说明message的来源,意图 - */ - public abstract void onShowMessage(String message, String intent); - - /** - * 当权限被拒回调该方法 - * - * @param permissions - * @param permissionType - * @param action - */ - public abstract void onPermissionsDeny(String[] permissions, String permissionType, String action); + public static boolean HAS_DESIGN_LIB = false; + private Activity mActivity; + private WebParentLayout mWebParentLayout; + private volatile boolean mIsBindWebParent = false; + protected AbsAgentWebUIController mAgentWebUIControllerDelegate; + protected String TAG = this.getClass().getSimpleName(); + + static { + try { + Class.forName("android.support.design.widget.Snackbar"); + Class.forName("android.support.design.widget.BottomSheetDialog"); + HAS_DESIGN_LIB = true; + } catch (Throwable ignore) { + HAS_DESIGN_LIB = false; + } + } + + protected AbsAgentWebUIController create() { + return HAS_DESIGN_LIB ? new DefaultDesignUIController() : new DefaultUIController(); + } + + protected AbsAgentWebUIController getDelegate() { + AbsAgentWebUIController mAgentWebUIController = this.mAgentWebUIControllerDelegate; + if (mAgentWebUIController == null) { + this.mAgentWebUIControllerDelegate = mAgentWebUIController = create(); + } + return mAgentWebUIController; + } + + final synchronized void bindWebParent(WebParentLayout webParentLayout, Activity activity) { + if (!mIsBindWebParent) { + mIsBindWebParent = true; + this.mWebParentLayout = webParentLayout; + this.mActivity = activity; + bindSupportWebParent(webParentLayout, activity); + } + } + + protected void toDismissDialog(Dialog dialog) { + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + } + + protected void toShowDialog(Dialog dialog) { + if (dialog != null && !dialog.isShowing()) { + dialog.show(); + } + } + + protected abstract void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity); + + /** + * WebChromeClient#onJsAlert + * + * @param view + * @param url + * @param message + */ + public abstract void onJsAlert(WebView view, String url, String message); + + /** + * 咨询用户是否前往其他页面 + * + * @param view + * @param url + * @param callback + */ + public abstract void onOpenPagePrompt(WebView view, String url, Handler.Callback callback); + + /** + * WebChromeClient#onJsConfirm + * + * @param view + * @param url + * @param message + * @param jsResult + */ + public abstract void onJsConfirm(WebView view, String url, String message, JsResult jsResult); + + public abstract void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback); + + /** + * 强制下载弹窗 + * + * @param url 当前下载地址。 + * @param callback 用户操作回调回调 + */ + public abstract void onForceDownloadAlert(String url, Handler.Callback callback); + + /** + * WebChromeClient#onJsPrompt + * + * @param view + * @param url + * @param message + * @param defaultValue + * @param jsPromptResult + */ + public abstract void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult); + + /** + * 显示错误页 + * + * @param view + * @param errorCode + * @param description + * @param failingUrl + */ + public abstract void onMainFrameError(WebView view, int errorCode, String description, String failingUrl); + + /** + * 隐藏错误页 + */ + public abstract void onShowMainFrame(); + + /** + * 正在加载... + * + * @param msg + */ + public abstract void onLoading(String msg); + + /** + * 取消正在加载... + */ + public abstract void onCancelLoading(); + + /** + * @param message 消息 + * @param intent 说明message的来源,意图 + */ + public abstract void onShowMessage(String message, String intent); + + /** + * 当权限被拒回调该方法 + * + * @param permissions + * @param permissionType + * @param action + */ + public abstract void onPermissionsDeny(String[] permissions, String permissionType, String action); } diff --git a/agentweb-core/src/main/java/com/just/agentweb/Action.java b/agentweb-core/src/main/java/com/just/agentweb/Action.java index 8af888f..9361722 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/Action.java +++ b/agentweb-core/src/main/java/com/just/agentweb/Action.java @@ -30,84 +30,81 @@ */ public class Action implements Parcelable { - public transient static final int ACTION_PERMISSION = 1; - public transient static final int ACTION_FILE = 2; - public transient static final int ACTION_CAMERA = 3; - private ArrayList mPermissions = new ArrayList(); - private int mAction; - private int mFromIntention; - - - public Action() { - - } - - public ArrayList getPermissions() { - return mPermissions; - } - - public void setPermissions(ArrayList permissions) { - this.mPermissions = permissions; - } - - public void setPermissions(String[] permissions) { - this.mPermissions = new ArrayList<>(Arrays.asList(permissions)); - } - - public int getAction() { - return mAction; - } - - public void setAction(int action) { - this.mAction = action; - } - - protected Action(Parcel in) { - mPermissions = in.createStringArrayList(); - mAction = in.readInt(); - mFromIntention = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStringList(mPermissions); - dest.writeInt(mAction); - dest.writeInt(mFromIntention); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Creator CREATOR = new Creator() { - @Override - public Action createFromParcel(Parcel in) { - return new Action(in); - } - - @Override - public Action[] newArray(int size) { - return new Action[size]; - } - }; - - public int getFromIntention() { - return mFromIntention; - } - - public static Action createPermissionsAction(String[] permissions) { - Action mAction = new Action(); - mAction.setAction(Action.ACTION_PERMISSION); - List mList = Arrays.asList(permissions); - mAction.setPermissions(new ArrayList(mList)); - return mAction; - } - - public Action setFromIntention(int fromIntention) { - this.mFromIntention = fromIntention; - return this; - } - - + public transient static final int ACTION_PERMISSION = 1; + public transient static final int ACTION_FILE = 2; + public transient static final int ACTION_CAMERA = 3; + public transient static final int ACTION_VIDEO = 4; + private ArrayList mPermissions = new ArrayList(); + private int mAction; + private int mFromIntention; + + public Action() { + } + + public ArrayList getPermissions() { + return mPermissions; + } + + public void setPermissions(ArrayList permissions) { + this.mPermissions = permissions; + } + + public void setPermissions(String[] permissions) { + this.mPermissions = new ArrayList<>(Arrays.asList(permissions)); + } + + public int getAction() { + return mAction; + } + + public void setAction(int action) { + this.mAction = action; + } + + protected Action(Parcel in) { + mPermissions = in.createStringArrayList(); + mAction = in.readInt(); + mFromIntention = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStringList(mPermissions); + dest.writeInt(mAction); + dest.writeInt(mFromIntention); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public Action createFromParcel(Parcel in) { + return new Action(in); + } + + @Override + public Action[] newArray(int size) { + return new Action[size]; + } + }; + + public int getFromIntention() { + return mFromIntention; + } + + public static Action createPermissionsAction(String[] permissions) { + Action mAction = new Action(); + mAction.setAction(Action.ACTION_PERMISSION); + List mList = Arrays.asList(permissions); + mAction.setPermissions(new ArrayList(mList)); + return mAction; + } + + public Action setFromIntention(int fromIntention) { + this.mFromIntention = fromIntention; + return this; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/ActionActivity.java b/agentweb-core/src/main/java/com/just/agentweb/ActionActivity.java index 410cc58..1a5c8cc 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/ActionActivity.java +++ b/agentweb-core/src/main/java/com/just/agentweb/ActionActivity.java @@ -21,8 +21,9 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.File; import java.util.List; @@ -30,9 +31,10 @@ import static android.provider.MediaStore.EXTRA_OUTPUT; + /** - * @author cenxiaozhong * @since 2.0.0 + * @author cenxiaozhong */ public final class ActionActivity extends Activity { @@ -47,9 +49,7 @@ public final class ActionActivity extends Activity { private Action mAction; public static final int REQUEST_CODE = 0x254; - public static void start(Activity activity, Action action) { - Intent mIntent = new Intent(activity, ActionActivity.class); mIntent.putExtra(KEY_ACTION, action); // mIntent.setExtrasClassLoader(Action.class.getClassLoader()); @@ -89,29 +89,26 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { permission(mAction); } else if (mAction.getAction() == Action.ACTION_CAMERA) { realOpenCamera(); + } else if (mAction.getAction() == Action.ACTION_VIDEO){ + realOpenVideo(); } else { fetchFile(mAction); } - } private void fetchFile(Action action) { - if (mChooserListener == null) { finish(); } - realOpenFileChooser(); } private void realOpenFileChooser() { - try { if (mChooserListener == null) { finish(); return; } - Intent mIntent = getIntent().getParcelableExtra(KEY_FILE_CHOOSER_INTENT); if (mIntent == null) { cancelAction(); @@ -125,7 +122,6 @@ private void realOpenFileChooser() { throwable.printStackTrace(); } } - } private void chooserActionCallback(int resultCode, Intent data) { @@ -138,16 +134,12 @@ private void chooserActionCallback(int resultCode, Intent data) { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - - if (requestCode == REQUEST_CODE) { chooserActionCallback(resultCode, mUri != null ? new Intent().putExtra(KEY_URI, mUri) : data); } } private void permission(Action action) { - - List permissions = action.getPermissions(); if (AgentWebUtils.isEmptyCollection(permissions)) { mPermissionListener = null; @@ -155,7 +147,6 @@ private void permission(Action action) { finish(); return; } - if (mRationaleListener != null) { boolean rationale = false; for (String permission : permissions) { @@ -169,19 +160,16 @@ private void permission(Action action) { finish(); return; } - - if (mPermissionListener != null) { + if (mPermissionListener != null){ requestPermissions(permissions.toArray(new String[]{}), 1); } - } private Uri mUri; private void realOpenCamera() { - try { - if (mChooserListener == null) { + if (mChooserListener == null){ finish(); } File mFile = AgentWebUtils.createImageFile(this); @@ -200,12 +188,37 @@ private void realOpenCamera() { mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); } mChooserListener = null; - if (LogUtils.isDebug()) { + if (LogUtils.isDebug()){ ignore.printStackTrace(); } } + } - + private void realOpenVideo(){ + try { + if (mChooserListener == null){ + finish(); + } + File mFile = AgentWebUtils.createVideoFile(this); + if (mFile == null) { + mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); + mChooserListener = null; + finish(); + } + Intent intent = AgentWebUtils.getIntentVideoCompat(this, mFile); + // 指定开启系统相机的Action + mUri = intent.getParcelableExtra(EXTRA_OUTPUT); + this.startActivityForResult(intent, REQUEST_CODE); + } catch (Throwable ignore) { + LogUtils.e(TAG, "找不到系统相机"); + if (mChooserListener != null) { + mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); + } + mChooserListener = null; + if (LogUtils.isDebug()){ + ignore.printStackTrace(); + } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWeb.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWeb.java index 70978af..712503f 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWeb.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWeb.java @@ -17,13 +17,6 @@ package com.just.agentweb; import android.app.Activity; -import android.support.annotation.ColorInt; -import android.support.annotation.IdRes; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.util.ArrayMap; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; @@ -32,6 +25,14 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.annotation.ColorInt; +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArrayMap; +import androidx.fragment.app.Fragment; + import java.lang.ref.WeakReference; import java.util.Map; @@ -41,778 +42,718 @@ * @since 1.0.0 */ public final class AgentWeb { - /** - * AgentWeb TAG - */ - private static final String TAG = AgentWeb.class.getSimpleName(); - /** - * Activity - */ - private Activity mActivity; - /** - * 承载 WebParentLayout 的 ViewGroup - */ - private ViewGroup mViewGroup; - /** - * 负责创建布局 WebView ,WebParentLayout Indicator等。 - */ - private WebCreator mWebCreator; - /** - * 管理 WebSettings - */ - private IAgentWebSettings mAgentWebSettings; - /** - * AgentWeb - */ - private AgentWeb mAgentWeb = null; - /** - * IndicatorController 控制Indicator - */ - private IndicatorController mIndicatorController; - /** - * WebChromeClient - */ - private WebChromeClient mWebChromeClient; - /** - * WebViewClient - */ - private WebViewClient mWebViewClient; - /** - * 是否启动进度条 - */ - private boolean mEnableIndicator; - /** - * IEventHandler 处理WebView相关返回事件 - */ - private IEventHandler mIEventHandler; - /** - * WebView 注入对象 - */ - private ArrayMap mJavaObjects = new ArrayMap<>(); - /** - * 用于表示当前在 Fragment 使用还是 Activity 上使用 - */ - private int TAG_TARGET = 0; - /** - * WebListenerManager - */ - private WebListenerManager mWebListenerManager; - /** - * 安全把控 - */ - private WebSecurityController mWebSecurityController = null; - /** - * WebSecurityCheckLogic - */ - private WebSecurityCheckLogic mWebSecurityCheckLogic = null; - /** - * WebChromeClient - */ - private WebChromeClient mTargetChromeClient; - /** - * 安全类型 - */ - private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK; - /** - * Activity 标识 - */ - private static final int ACTIVITY_TAG = 0; - /** - * Fragment 标识 - */ - private static final int FRAGMENT_TAG = 1; - /** - * AgentWeb 注入对象 - */ - private AgentWebJsInterfaceCompat mAgentWebJsInterfaceCompat = null; - /** - * JsAccessEntrace 提供快速的JS调用 - */ - private JsAccessEntrace mJsAccessEntrace = null; - /** - * URL Loader , 封装了 mWebView.loadUrl(url) reload() stopLoading() postUrl()等方法 - */ - private IUrlLoader mIUrlLoader = null; - /** - * WebView 生命周期 , 适当的释放CPU - */ - private WebLifeCycle mWebLifeCycle; - /** - * Video 视频播放类 - */ - private IVideo mIVideo = null; - /** - * WebViewClient 辅助控制开关 - */ - private boolean mWebClientHelper = true; - /** - * PermissionInterceptor 权限拦截 - */ - private PermissionInterceptor mPermissionInterceptor; - /** - * 是否拦截未知的Url, 用于 DefaultWebClient - */ - private boolean mIsInterceptUnkownUrl = false; - /** - * 该变量控制了是否咨询用户页面跳转,或者直接拦截 - */ - private int mUrlHandleWays = -1; - /** - * MiddlewareWebClientBase WebViewClient 中间件 - */ - private MiddlewareWebClientBase mMiddleWrareWebClientBaseHeader; - /** - * MiddlewareWebChromeBase WebChromeClient 中间件 - */ - private MiddlewareWebChromeBase mMiddlewareWebChromeBaseHeader; - /** - * 事件拦截 - */ - private EventInterceptor mEventInterceptor; - - - private JsInterfaceHolder mJsInterfaceHolder = null; - - - private AgentWeb(AgentBuilder agentBuilder) { - TAG_TARGET = agentBuilder.mTag; - this.mActivity = agentBuilder.mActivity; - this.mViewGroup = agentBuilder.mViewGroup; - this.mIEventHandler = agentBuilder.mIEventHandler; - this.mEnableIndicator = agentBuilder.mEnableIndicator; - mWebCreator = agentBuilder.mWebCreator == null ? configWebCreator(agentBuilder.mBaseIndicatorView, agentBuilder.mIndex, agentBuilder.mLayoutParams, agentBuilder.mIndicatorColor, agentBuilder.mHeight, agentBuilder.mWebView, agentBuilder.mWebLayout) : agentBuilder.mWebCreator; - mIndicatorController = agentBuilder.mIndicatorController; - this.mWebChromeClient = agentBuilder.mWebChromeClient; - this.mWebViewClient = agentBuilder.mWebViewClient; - mAgentWeb = this; - this.mAgentWebSettings = agentBuilder.mAgentWebSettings; - - if (agentBuilder.mJavaObject != null && !agentBuilder.mJavaObject.isEmpty()) { - this.mJavaObjects.putAll((Map) agentBuilder.mJavaObject); - LogUtils.i(TAG, "mJavaObject size:" + this.mJavaObjects.size()); - - } - this.mPermissionInterceptor = agentBuilder.mPermissionInterceptor == null ? null : new PermissionInterceptorWrapper(agentBuilder.mPermissionInterceptor); - this.mSecurityType = agentBuilder.mSecurityType; - this.mIUrlLoader = new UrlLoaderImpl(mWebCreator.create().getWebView(), agentBuilder.mHttpHeaders); - if (this.mWebCreator.getWebParentLayout() instanceof WebParentLayout) { - WebParentLayout mWebParentLayout = (WebParentLayout) this.mWebCreator.getWebParentLayout(); - mWebParentLayout.bindController(agentBuilder.mAgentWebUIController == null ? AgentWebUIControllerImplBase.build() : agentBuilder.mAgentWebUIController); - mWebParentLayout.setErrorLayoutRes(agentBuilder.mErrorLayout, agentBuilder.mReloadId); - mWebParentLayout.setErrorView(agentBuilder.mErrorView); - } - this.mWebLifeCycle = new DefaultWebLifeCycleImpl(mWebCreator.getWebView()); - mWebSecurityController = new WebSecurityControllerImpl(mWebCreator.getWebView(), this.mAgentWeb.mJavaObjects, this.mSecurityType); - this.mWebClientHelper = agentBuilder.mWebClientHelper; - this.mIsInterceptUnkownUrl = agentBuilder.mIsInterceptUnkownUrl; - if (agentBuilder.mOpenOtherPage != null) { - this.mUrlHandleWays = agentBuilder.mOpenOtherPage.code; - } - this.mMiddleWrareWebClientBaseHeader = agentBuilder.mMiddlewareWebClientBaseHeader; - this.mMiddlewareWebChromeBaseHeader = agentBuilder.mChromeMiddleWareHeader; - init(); - } - - - /** - * @return PermissionInterceptor 权限控制者 - */ - public PermissionInterceptor getPermissionInterceptor() { - return this.mPermissionInterceptor; - } - - - public WebLifeCycle getWebLifeCycle() { - return this.mWebLifeCycle; - } - - - public JsAccessEntrace getJsAccessEntrace() { - - JsAccessEntrace mJsAccessEntrace = this.mJsAccessEntrace; - if (mJsAccessEntrace == null) { - this.mJsAccessEntrace = mJsAccessEntrace = JsAccessEntraceImpl.getInstance(mWebCreator.getWebView()); - } - return mJsAccessEntrace; - } - - - public AgentWeb clearWebCache() { - - if (this.getWebCreator().getWebView() != null) { - AgentWebUtils.clearWebViewAllCache(mActivity, this.getWebCreator().getWebView()); - } else { - AgentWebUtils.clearWebViewAllCache(mActivity); - } - return this; - } - - - public static AgentBuilder with(@NonNull Activity activity) { - if (activity == null) { - throw new NullPointerException("activity can not be null ."); - } - return new AgentBuilder(activity); - } - - public static AgentBuilder with(@NonNull Fragment fragment) { - - - Activity mActivity = null; - if ((mActivity = fragment.getActivity()) == null) { - throw new NullPointerException("activity can not be null ."); - } - return new AgentBuilder(mActivity, fragment); - } - - public boolean handleKeyEvent(int keyCode, KeyEvent keyEvent) { - - if (mIEventHandler == null) { - mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor()); - } - return mIEventHandler.onKeyDown(keyCode, keyEvent); - } - - public boolean back() { - - if (mIEventHandler == null) { - mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor()); - } - return mIEventHandler.back(); - } - - - public WebCreator getWebCreator() { - return this.mWebCreator; - } - - public IEventHandler getIEventHandler() { - return this.mIEventHandler == null ? (this.mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor())) : this.mIEventHandler; - } - - - public IAgentWebSettings getAgentWebSettings() { - return this.mAgentWebSettings; - } - - public IndicatorController getIndicatorController() { - return this.mIndicatorController; - } - - public JsInterfaceHolder getJsInterfaceHolder() { - return this.mJsInterfaceHolder; - } - - public IUrlLoader getUrlLoader() { - return this.mIUrlLoader; - } - - public void destroy() { - this.mWebLifeCycle.onDestroy(); - } - - /** - * 应该是对外的包装? - */ - public static class PreAgentWeb { - private AgentWeb mAgentWeb; - private boolean isReady = false; - - PreAgentWeb(AgentWeb agentWeb) { - this.mAgentWeb = agentWeb; - } - - - public PreAgentWeb ready() { - if (!isReady) { - mAgentWeb.ready(); - isReady = true; - } - return this; - } - - public AgentWeb loadUrl(@Nullable String url) { - if (!isReady) { - ready(); - } - return mAgentWeb.loadUrl(url); - } - - public AgentWeb loadUrl(@Nullable String url, Map additionalHttpHeaders) { - if (!isReady) { - ready(); - } - return mAgentWeb.loadUrl(url, additionalHttpHeaders); - } - - - public AgentWeb loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { - if (!isReady) { - ready(); - } - return mAgentWeb.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); - } - - } - - - private void doSafeCheck() { - - WebSecurityCheckLogic mWebSecurityCheckLogic = this.mWebSecurityCheckLogic; - if (mWebSecurityCheckLogic == null) { - this.mWebSecurityCheckLogic = mWebSecurityCheckLogic = WebSecurityLogicImpl.getInstance(); - } - mWebSecurityController.check(mWebSecurityCheckLogic); - - } - - private void doCompat() { - mJavaObjects.put("agentWeb", mAgentWebJsInterfaceCompat = new AgentWebJsInterfaceCompat(this, mActivity)); - } - - private WebCreator configWebCreator(BaseIndicatorView progressView, int index, ViewGroup.LayoutParams lp, int indicatorColor, int height_dp, WebView webView, IWebLayout webLayout) { - - if (progressView != null && mEnableIndicator) { - return new DefaultWebCreator(mActivity, mViewGroup, lp, index, progressView, webView, webLayout); - } else { - return mEnableIndicator ? - new DefaultWebCreator(mActivity, mViewGroup, lp, index, indicatorColor, height_dp, webView, webLayout) - : new DefaultWebCreator(mActivity, mViewGroup, lp, index, webView, webLayout); - } - } - - public AgentWeb loadUrl(String url) { - this.getUrlLoader().loadUrl(url); - IndicatorController mIndicatorController = null; - if (!TextUtils.isEmpty(url) && (mIndicatorController = getIndicatorController()) != null && mIndicatorController.offerIndicator() != null) { - getIndicatorController().offerIndicator().show(); - } - return this; - } - - // TODO: 2018/6/10 wizos - private AgentWeb loadUrl(String url, Map additionalHttpHeaders) { - this.getUrlLoader().loadUrl(url, additionalHttpHeaders); - IndicatorController mIndicatorController = null; - if (!TextUtils.isEmpty(url) && (mIndicatorController = getIndicatorController()) != null && mIndicatorController.offerIndicator() != null) { - getIndicatorController().offerIndicator().show(); - } - return this; - } - - // TODO: 2018/4/1 wizos: - private AgentWeb loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { - this.getUrlLoader().loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); - IndicatorController mIndicatorController = null; - if (!TextUtils.isEmpty(data) && (mIndicatorController = getIndicatorController()) != null && mIndicatorController.offerIndicator() != null) { - getIndicatorController().offerIndicator().show(); - } - return this; - } - - - private EventInterceptor getInterceptor() { - - if (this.mEventInterceptor != null) { - return this.mEventInterceptor; - } - - if (mIVideo instanceof VideoImpl) { - return this.mEventInterceptor = (EventInterceptor) this.mIVideo; - } - - return null; - - } - - private void init() { - doCompat(); - doSafeCheck(); - } - - private IVideo getIVideo() { - return mIVideo == null ? new VideoImpl(mActivity, mWebCreator.getWebView()) : mIVideo; - } - - private WebViewClient getWebViewClient() { - - LogUtils.i(TAG, "getDelegate:" + this.mMiddleWrareWebClientBaseHeader); - DefaultWebClient mDefaultWebClient = DefaultWebClient - .createBuilder() - .setActivity(this.mActivity) - .setClient(this.mWebViewClient) - .setWebClientHelper(this.mWebClientHelper) - .setPermissionInterceptor(this.mPermissionInterceptor) - .setWebView(this.mWebCreator.getWebView()) - .setInterceptUnkownUrl(this.mIsInterceptUnkownUrl) - .setUrlHandleWays(this.mUrlHandleWays) - .build(); - MiddlewareWebClientBase header = this.mMiddleWrareWebClientBaseHeader; - if (header != null) { - MiddlewareWebClientBase tail = header; - int count = 1; - MiddlewareWebClientBase tmp = header; - while (tmp.next() != null) { - tail = tmp = tmp.next(); - count++; - } - LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count); - tail.setDelegate(mDefaultWebClient); - return header; - } else { - return mDefaultWebClient; - } - - } - - /** - * 设置 WebSettings - * - * @return - */ - private AgentWeb ready() { - - AgentWebConfig.initCookiesManager(mActivity.getApplicationContext()); - IAgentWebSettings mAgentWebSettings = this.mAgentWebSettings; - if (mAgentWebSettings == null) { - this.mAgentWebSettings = mAgentWebSettings = AgentWebSettingsImpl.getInstance(); - } - - if (mAgentWebSettings instanceof AbsAgentWebSettings) { - ((AbsAgentWebSettings) mAgentWebSettings).bindAgentWeb(this); - } - if (mWebListenerManager == null && mAgentWebSettings instanceof AbsAgentWebSettings) { - mWebListenerManager = (WebListenerManager) mAgentWebSettings; - } - mAgentWebSettings.toSetting(mWebCreator.getWebView()); - if (mJsInterfaceHolder == null) { - mJsInterfaceHolder = JsInterfaceHolderImpl.getJsInterfaceHolder(mWebCreator.getWebView(), this.mSecurityType); - } - LogUtils.i(TAG, "mJavaObjects:" + mJavaObjects.size()); - if (mJavaObjects != null && !mJavaObjects.isEmpty()) { - mJsInterfaceHolder.addJavaObjects(mJavaObjects); - } - - if (mWebListenerManager != null) { - mWebListenerManager.setDownloader(mWebCreator.getWebView(), null); - mWebListenerManager.setWebChromeClient(mWebCreator.getWebView(), getChromeClient()); - mWebListenerManager.setWebViewClient(mWebCreator.getWebView(), getWebViewClient()); - } - - return this; - } - - private WebChromeClient getChromeClient() { - IndicatorController mIndicatorController = - (this.mIndicatorController == null) ? - IndicatorHandler.getInstance().inJectIndicator(mWebCreator.offer()) - : this.mIndicatorController; - - DefaultChromeClient mDefaultChromeClient = - new DefaultChromeClient(this.mActivity, - this.mIndicatorController = mIndicatorController, - this.mWebChromeClient, this.mIVideo = getIVideo(), - this.mPermissionInterceptor, mWebCreator.getWebView()); - - LogUtils.i(TAG, "WebChromeClient:" + this.mWebChromeClient); - MiddlewareWebChromeBase header = this.mMiddlewareWebChromeBaseHeader; - if (header != null) { - MiddlewareWebChromeBase tail = header; - int count = 1; - MiddlewareWebChromeBase tmp = header; - for (; tmp.next() != null; ) { - tail = tmp = tmp.next(); - count++; - } - LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count); - tail.setDelegate(mDefaultChromeClient); - return this.mTargetChromeClient = header; - } else { - return this.mTargetChromeClient = mDefaultChromeClient; - } - } - - - public enum SecurityType { - DEFAULT_CHECK, STRICT_CHECK - } - - - public static final class AgentBuilder { - private Activity mActivity; - private Fragment mFragment; - private ViewGroup mViewGroup; - private boolean mIsNeedDefaultProgress; - private int mIndex = -1; - private BaseIndicatorView mBaseIndicatorView; - private IndicatorController mIndicatorController = null; - /*默认进度条是显示的*/ - private boolean mEnableIndicator = true; - private ViewGroup.LayoutParams mLayoutParams = null; - private WebViewClient mWebViewClient; - private WebChromeClient mWebChromeClient; - private int mIndicatorColor = -1; - private IAgentWebSettings mAgentWebSettings; - private WebCreator mWebCreator; - private HttpHeaders mHttpHeaders = null; - private IEventHandler mIEventHandler; - private int mHeight = -1; - private ArrayMap mJavaObject; - private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK; - private WebView mWebView; - private boolean mWebClientHelper = true; - private IWebLayout mWebLayout = null; - private PermissionInterceptor mPermissionInterceptor = null; - private AbsAgentWebUIController mAgentWebUIController; - private DefaultWebClient.OpenOtherPageWays mOpenOtherPage = null; - private boolean mIsInterceptUnkownUrl = false; - private MiddlewareWebClientBase mMiddlewareWebClientBaseHeader; - private MiddlewareWebClientBase mMiddlewareWebClientBaseTail; - private MiddlewareWebChromeBase mChromeMiddleWareHeader = null; - private MiddlewareWebChromeBase mChromeMiddleWareTail = null; - private View mErrorView; - private int mErrorLayout; - private int mReloadId; - private int mTag = -1; - - - public AgentBuilder(@NonNull Activity activity, @NonNull Fragment fragment) { - mActivity = activity; - mFragment = fragment; - mTag = AgentWeb.FRAGMENT_TAG; - } - - public AgentBuilder(@NonNull Activity activity) { - mActivity = activity; - mTag = AgentWeb.ACTIVITY_TAG; - } - - - public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, @NonNull ViewGroup.LayoutParams lp) { - this.mViewGroup = v; - this.mLayoutParams = lp; - return new IndicatorBuilder(this); - } - - public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, int index, @NonNull ViewGroup.LayoutParams lp) { - this.mViewGroup = v; - this.mLayoutParams = lp; - this.mIndex = index; - return new IndicatorBuilder(this); - } - - - private PreAgentWeb buildAgentWeb() { - if (mTag == AgentWeb.FRAGMENT_TAG && this.mViewGroup == null) { - throw new NullPointerException("ViewGroup is null,Please check your parameters ."); - } - return new PreAgentWeb(HookManager.hookAgentWeb(new AgentWeb(this), this)); - } - - private void addJavaObject(String key, Object o) { - if (mJavaObject == null) { - mJavaObject = new ArrayMap<>(); - } - mJavaObject.put(key, o); - } - - private void addHeader(String k, String v) { - - if (mHttpHeaders == null) { - mHttpHeaders = HttpHeaders.create(); - } - mHttpHeaders.additionalHttpHeader(k, v); - - } - } - - public static class IndicatorBuilder { - private AgentBuilder mAgentBuilder = null; - - public IndicatorBuilder(AgentBuilder agentBuilder) { - this.mAgentBuilder = agentBuilder; - } - - public CommonBuilder useDefaultIndicator(int color) { - this.mAgentBuilder.mEnableIndicator = true; - this.mAgentBuilder.mIndicatorColor = color; - return new CommonBuilder(mAgentBuilder); - } - - public CommonBuilder useDefaultIndicator() { - this.mAgentBuilder.mEnableIndicator = true; - return new CommonBuilder(mAgentBuilder); - } - - public CommonBuilder closeIndicator() { - this.mAgentBuilder.mEnableIndicator = false; - this.mAgentBuilder.mIndicatorColor = -1; - this.mAgentBuilder.mHeight = -1; - return new CommonBuilder(mAgentBuilder); - } - - public CommonBuilder setCustomIndicator(@NonNull BaseIndicatorView v) { - if (v != null) { - this.mAgentBuilder.mEnableIndicator = true; - this.mAgentBuilder.mBaseIndicatorView = v; - this.mAgentBuilder.mIsNeedDefaultProgress = false; - } else { - this.mAgentBuilder.mEnableIndicator = true; - this.mAgentBuilder.mIsNeedDefaultProgress = true; - } - - return new CommonBuilder(mAgentBuilder); - } - - public CommonBuilder useDefaultIndicator(@ColorInt int color, int height_dp) { - this.mAgentBuilder.mIndicatorColor = color; - this.mAgentBuilder.mHeight = height_dp; - return new CommonBuilder(this.mAgentBuilder); - } - - } - - - public static class CommonBuilder { - private AgentBuilder mAgentBuilder; - - public CommonBuilder(AgentBuilder agentBuilder) { - this.mAgentBuilder = agentBuilder; - } - - public CommonBuilder setEventHanadler(@Nullable IEventHandler iEventHandler) { - mAgentBuilder.mIEventHandler = iEventHandler; - return this; - } - - public CommonBuilder closeWebViewClientHelper() { - mAgentBuilder.mWebClientHelper = false; - return this; - } - - - public CommonBuilder setWebChromeClient(@Nullable WebChromeClient webChromeClient) { - this.mAgentBuilder.mWebChromeClient = webChromeClient; - return this; - - } - - public CommonBuilder setWebViewClient(@Nullable WebViewClient webChromeClient) { - this.mAgentBuilder.mWebViewClient = webChromeClient; - return this; - } - - public CommonBuilder useMiddlewareWebClient(@NonNull MiddlewareWebClientBase middleWrareWebClientBase) { - if (middleWrareWebClientBase == null) { - return this; - } - if (this.mAgentBuilder.mMiddlewareWebClientBaseHeader == null) { - this.mAgentBuilder.mMiddlewareWebClientBaseHeader = this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase; - } else { - this.mAgentBuilder.mMiddlewareWebClientBaseTail.enq(middleWrareWebClientBase); - this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase; - } - return this; - } - - public CommonBuilder useMiddlewareWebChrome(@NonNull MiddlewareWebChromeBase middlewareWebChromeBase) { - if (middlewareWebChromeBase == null) { - return this; - } - if (this.mAgentBuilder.mChromeMiddleWareHeader == null) { - this.mAgentBuilder.mChromeMiddleWareHeader = this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase; - } else { - this.mAgentBuilder.mChromeMiddleWareTail.enq(middlewareWebChromeBase); - this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase; - } - return this; - } - - public CommonBuilder setMainFrameErrorView(@NonNull View view) { - this.mAgentBuilder.mErrorView = view; - return this; - } - - public CommonBuilder setMainFrameErrorView(@LayoutRes int errorLayout, @IdRes int clickViewId) { - this.mAgentBuilder.mErrorLayout = errorLayout; - this.mAgentBuilder.mReloadId = clickViewId; - return this; - } - - public CommonBuilder setAgentWebWebSettings(@Nullable IAgentWebSettings agentWebSettings) { - this.mAgentBuilder.mAgentWebSettings = agentWebSettings; - return this; - } - - public PreAgentWeb createAgentWeb() { - return this.mAgentBuilder.buildAgentWeb(); - } - - - public CommonBuilder addJavascriptInterface(@NonNull String name, @NonNull Object o) { - this.mAgentBuilder.addJavaObject(name, o); - return this; - } - - public CommonBuilder setSecurityType(@NonNull SecurityType type) { - this.mAgentBuilder.mSecurityType = type; - return this; - } - - public CommonBuilder setWebView(@Nullable WebView webView) { - this.mAgentBuilder.mWebView = webView; - return this; - } - - public CommonBuilder setWebLayout(@Nullable IWebLayout iWebLayout) { - this.mAgentBuilder.mWebLayout = iWebLayout; - return this; - } - - public CommonBuilder additionalHttpHeader(String k, String v) { - this.mAgentBuilder.addHeader(k, v); - - return this; - } - - public CommonBuilder setPermissionInterceptor(@Nullable PermissionInterceptor permissionInterceptor) { - this.mAgentBuilder.mPermissionInterceptor = permissionInterceptor; - return this; - } - - public CommonBuilder setAgentWebUIController(@Nullable AgentWebUIControllerImplBase agentWebUIController) { - this.mAgentBuilder.mAgentWebUIController = agentWebUIController; - return this; - } - - public CommonBuilder setOpenOtherPageWays(@Nullable DefaultWebClient.OpenOtherPageWays openOtherPageWays) { - this.mAgentBuilder.mOpenOtherPage = openOtherPageWays; - return this; - } - - public CommonBuilder interceptUnkownUrl() { - this.mAgentBuilder.mIsInterceptUnkownUrl = true; - return this; - } - - } - - private static final class PermissionInterceptorWrapper implements PermissionInterceptor { - - private WeakReference mWeakReference; - - private PermissionInterceptorWrapper(PermissionInterceptor permissionInterceptor) { - this.mWeakReference = new WeakReference(permissionInterceptor); - } - - @Override - public boolean intercept(String url, String[] permissions, String a) { - if (this.mWeakReference.get() == null) { - return false; - } - return mWeakReference.get().intercept(url, permissions, a); - } - } - - + /** + * AgentWeb 's TAG + */ + private static final String TAG = AgentWeb.class.getSimpleName(); + /** + * Activity + */ + private Activity mActivity; + /** + * 承载 WebParentLayout 的 ViewGroup + */ + private ViewGroup mViewGroup; + /** + * 负责创建布局 WebView ,WebParentLayout Indicator等。 + */ + private WebCreator mWebCreator; + /** + * 管理 WebSettings + */ + private IAgentWebSettings mAgentWebSettings; + /** + * AgentWeb + */ + private AgentWeb mAgentWeb = null; + /** + * IndicatorController 控制Indicator + */ + private IndicatorController mIndicatorController; + /** + * WebChromeClient + */ + private com.just.agentweb.WebChromeClient mWebChromeClient; + /** + * WebViewClient + */ + private com.just.agentweb.WebViewClient mWebViewClient; + /** + * is show indicator + */ + private boolean mEnableIndicator; + /** + * IEventHandler 处理WebView相关返回事件 + */ + private IEventHandler mIEventHandler; + /** + * WebView 注入对象 + */ + private ArrayMap mJavaObjects = new ArrayMap<>(); + /** + * flag + */ + private int TAG_TARGET = 0; + /** + * WebListenerManager + */ + private WebListenerManager mWebListenerManager; + /** + * 安全 Controller + */ + private WebSecurityController mWebSecurityController = null; + /** + * WebSecurityCheckLogic + */ + private WebSecurityCheckLogic mWebSecurityCheckLogic = null; + /** + * WebChromeClient + */ + private WebChromeClient mTargetChromeClient; + /** + * flag security 's mode + */ + private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK; + /** + * Activity + */ + private static final int ACTIVITY_TAG = 0; + /** + * Fragment + */ + private static final int FRAGMENT_TAG = 1; + /** + * AgentWeb 默认注入对像 + */ + private AgentWebJsInterfaceCompat mAgentWebJsInterfaceCompat = null; + /** + * JsAccessEntrace 提供快速JS方法调用 + */ + private JsAccessEntrace mJsAccessEntrace = null; + /** + * URL Loader , 提供了 WebView#loadUrl(url) reload() stopLoading() postUrl()等方法 + */ + private IUrlLoader mIUrlLoader = null; + /** + * WebView 生命周期 , 跟随生命周期释放CPU + */ + private WebLifeCycle mWebLifeCycle; + /** + * Video 视屏播放管理类 + */ + private IVideo mIVideo = null; + /** + * WebViewClient 辅助控制开关 + */ + private boolean mWebClientHelper = true; + /** + * PermissionInterceptor 权限拦截 + */ + private PermissionInterceptor mPermissionInterceptor; + /** + * 是否拦截未知的Url, @link{DefaultWebClient} + */ + private boolean mIsInterceptUnkownUrl = false; + private int mUrlHandleWays = -1; + /** + * MiddlewareWebClientBase WebViewClient 中间件 + */ + private MiddlewareWebClientBase mMiddleWrareWebClientBaseHeader; + /** + * MiddlewareWebChromeBase WebChromeClient 中间件 + */ + private MiddlewareWebChromeBase mMiddlewareWebChromeBaseHeader; + /** + * 事件拦截 + */ + private EventInterceptor mEventInterceptor; + /** + * 注入对象管理类 + */ + private JsInterfaceHolder mJsInterfaceHolder = null; + + + private AgentWeb(AgentBuilder agentBuilder) { + TAG_TARGET = agentBuilder.mTag; + this.mActivity = agentBuilder.mActivity; + this.mViewGroup = agentBuilder.mViewGroup; + this.mIEventHandler = agentBuilder.mIEventHandler; + this.mEnableIndicator = agentBuilder.mEnableIndicator; + mWebCreator = agentBuilder.mWebCreator == null ? configWebCreator(agentBuilder.mBaseIndicatorView, agentBuilder.mIndex, agentBuilder.mLayoutParams, agentBuilder.mIndicatorColor, agentBuilder.mHeight, agentBuilder.mWebView, agentBuilder.mWebLayout) : agentBuilder.mWebCreator; + mIndicatorController = agentBuilder.mIndicatorController; + this.mWebChromeClient = agentBuilder.mWebChromeClient; + this.mWebViewClient = agentBuilder.mWebViewClient; + mAgentWeb = this; + this.mAgentWebSettings = agentBuilder.mAgentWebSettings; + + if (agentBuilder.mJavaObject != null && !agentBuilder.mJavaObject.isEmpty()) { + this.mJavaObjects.putAll((Map) agentBuilder.mJavaObject); + LogUtils.i(TAG, "mJavaObject size:" + this.mJavaObjects.size()); + + } + this.mPermissionInterceptor = agentBuilder.mPermissionInterceptor == null ? null : new PermissionInterceptorWrapper(agentBuilder.mPermissionInterceptor); + this.mSecurityType = agentBuilder.mSecurityType; + this.mIUrlLoader = new UrlLoaderImpl(mWebCreator.create().getWebView(), agentBuilder.mHttpHeaders); + if (this.mWebCreator.getWebParentLayout() instanceof WebParentLayout) { + WebParentLayout mWebParentLayout = (WebParentLayout) this.mWebCreator.getWebParentLayout(); + mWebParentLayout.bindController(agentBuilder.mAgentWebUIController == null ? AgentWebUIControllerImplBase.build() : agentBuilder.mAgentWebUIController); + mWebParentLayout.setErrorLayoutRes(agentBuilder.mErrorLayout, agentBuilder.mReloadId); + mWebParentLayout.setErrorView(agentBuilder.mErrorView); + } + this.mWebLifeCycle = new DefaultWebLifeCycleImpl(mWebCreator.getWebView()); + mWebSecurityController = new WebSecurityControllerImpl(mWebCreator.getWebView(), this.mAgentWeb.mJavaObjects, this.mSecurityType); + this.mWebClientHelper = agentBuilder.mWebClientHelper; + this.mIsInterceptUnkownUrl = agentBuilder.mIsInterceptUnkownUrl; + if (agentBuilder.mOpenOtherPage != null) { + this.mUrlHandleWays = agentBuilder.mOpenOtherPage.code; + } + this.mMiddleWrareWebClientBaseHeader = agentBuilder.mMiddlewareWebClientBaseHeader; + this.mMiddlewareWebChromeBaseHeader = agentBuilder.mChromeMiddleWareHeader; + init(); + } + + + /** + * @return PermissionInterceptor 权限控制者 + */ + public PermissionInterceptor getPermissionInterceptor() { + return this.mPermissionInterceptor; + } + + public WebLifeCycle getWebLifeCycle() { + return this.mWebLifeCycle; + } + + public JsAccessEntrace getJsAccessEntrace() { + JsAccessEntrace mJsAccessEntrace = this.mJsAccessEntrace; + if (mJsAccessEntrace == null) { + this.mJsAccessEntrace = mJsAccessEntrace = JsAccessEntraceImpl.getInstance(mWebCreator.getWebView()); + } + return mJsAccessEntrace; + } + + + public AgentWeb clearWebCache() { + if (this.getWebCreator().getWebView() != null) { + AgentWebUtils.clearWebViewAllCache(mActivity, this.getWebCreator().getWebView()); + } else { + AgentWebUtils.clearWebViewAllCache(mActivity); + } + return this; + } + + + public static AgentBuilder with(@NonNull Activity activity) { + if (activity == null) { + throw new NullPointerException("activity can not be null ."); + } + return new AgentBuilder(activity); + } + + public static AgentBuilder with(@NonNull Fragment fragment) { + Activity mActivity = null; + if ((mActivity = fragment.getActivity()) == null) { + throw new NullPointerException("activity can not be null ."); + } + return new AgentBuilder(mActivity, fragment); + } + + public boolean handleKeyEvent(int keyCode, KeyEvent keyEvent) { + if (mIEventHandler == null) { + mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor()); + } + return mIEventHandler.onKeyDown(keyCode, keyEvent); + } + + public boolean back() { + if (mIEventHandler == null) { + mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor()); + } + return mIEventHandler.back(); + } + + + public WebCreator getWebCreator() { + return this.mWebCreator; + } + + public IEventHandler getIEventHandler() { + return this.mIEventHandler == null ? (this.mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor())) : this.mIEventHandler; + } + + + public IAgentWebSettings getAgentWebSettings() { + return this.mAgentWebSettings; + } + + public IndicatorController getIndicatorController() { + return this.mIndicatorController; + } + + public JsInterfaceHolder getJsInterfaceHolder() { + return this.mJsInterfaceHolder; + } + + public IUrlLoader getUrlLoader() { + return this.mIUrlLoader; + } + + public void destroy() { + this.mWebLifeCycle.onDestroy(); + } + + public static class PreAgentWeb { + private AgentWeb mAgentWeb; + private boolean isReady = false; + + PreAgentWeb(AgentWeb agentWeb) { + this.mAgentWeb = agentWeb; + } + + public PreAgentWeb ready() { + if (!isReady) { + mAgentWeb.ready(); + isReady = true; + } + return this; + } + + public AgentWeb get() { + ready(); + return mAgentWeb; + } + + public AgentWeb go(@Nullable String url) { + if (!isReady) { + ready(); + } + return mAgentWeb.go(url); + } + } + + private void doSafeCheck() { + WebSecurityCheckLogic mWebSecurityCheckLogic = this.mWebSecurityCheckLogic; + if (mWebSecurityCheckLogic == null) { + this.mWebSecurityCheckLogic = mWebSecurityCheckLogic = WebSecurityLogicImpl.getInstance(); + } + mWebSecurityController.check(mWebSecurityCheckLogic); + } + + private void doCompat() { + mJavaObjects.put("agentWeb", mAgentWebJsInterfaceCompat = new AgentWebJsInterfaceCompat(this, mActivity)); + } + + private WebCreator configWebCreator(BaseIndicatorView progressView, int index, ViewGroup.LayoutParams lp, int indicatorColor, int height_dp, WebView webView, IWebLayout webLayout) { + if (progressView != null && mEnableIndicator) { + return new DefaultWebCreator(mActivity, mViewGroup, lp, index, progressView, webView, webLayout); + } else { + return mEnableIndicator ? + new DefaultWebCreator(mActivity, mViewGroup, lp, index, indicatorColor, height_dp, webView, webLayout) + : new DefaultWebCreator(mActivity, mViewGroup, lp, index, webView, webLayout); + } + } + + private AgentWeb go(String url) { + this.getUrlLoader().loadUrl(url); + IndicatorController mIndicatorController = null; + if (!TextUtils.isEmpty(url) && (mIndicatorController = getIndicatorController()) != null && mIndicatorController.offerIndicator() != null) { + getIndicatorController().offerIndicator().show(); + } + return this; + } + + private EventInterceptor getInterceptor() { + if (this.mEventInterceptor != null) { + return this.mEventInterceptor; + } + if (mIVideo instanceof VideoImpl) { + return this.mEventInterceptor = (EventInterceptor) this.mIVideo; + } + return null; + } + + private void init() { + doCompat(); + doSafeCheck(); + } + + private IVideo getIVideo() { + return mIVideo == null ? new VideoImpl(mActivity, mWebCreator.getWebView()) : mIVideo; + } + + private WebViewClient getWebViewClient() { + + LogUtils.i(TAG, "getDelegate:" + this.mMiddleWrareWebClientBaseHeader); + DefaultWebClient mDefaultWebClient = DefaultWebClient + .createBuilder() + .setActivity(this.mActivity) + .setWebClientHelper(this.mWebClientHelper) + .setPermissionInterceptor(this.mPermissionInterceptor) + .setWebView(this.mWebCreator.getWebView()) + .setInterceptUnkownUrl(this.mIsInterceptUnkownUrl) + .setUrlHandleWays(this.mUrlHandleWays) + .build(); + MiddlewareWebClientBase header = this.mMiddleWrareWebClientBaseHeader; + if (this.mWebViewClient != null) { + this.mWebViewClient.enq(this.mMiddleWrareWebClientBaseHeader); + header = this.mWebViewClient; + } + if (header != null) { + MiddlewareWebClientBase tail = header; + int count = 1; + MiddlewareWebClientBase tmp = header; + while (tmp.next() != null) { + tail = tmp = tmp.next(); + count++; + } + LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count); + tail.setDelegate(mDefaultWebClient); + return header; + } else { + return mDefaultWebClient; + } + } + + private AgentWeb ready() { + AgentWebConfig.initCookiesManager(mActivity.getApplicationContext()); + IAgentWebSettings mAgentWebSettings = this.mAgentWebSettings; + if (mAgentWebSettings == null) { + this.mAgentWebSettings = mAgentWebSettings = AgentWebSettingsImpl.getInstance(); + } + if (mAgentWebSettings instanceof AbsAgentWebSettings) { + ((AbsAgentWebSettings) mAgentWebSettings).bindAgentWeb(this); + } + if (mWebListenerManager == null && mAgentWebSettings instanceof AbsAgentWebSettings) { + mWebListenerManager = (WebListenerManager) mAgentWebSettings; + } + mAgentWebSettings.toSetting(mWebCreator.getWebView()); + if (mJsInterfaceHolder == null) { + mJsInterfaceHolder = JsInterfaceHolderImpl.getJsInterfaceHolder(mWebCreator.getWebView(), this.mSecurityType); + } + LogUtils.i(TAG, "mJavaObjects:" + mJavaObjects.size()); + if (mJavaObjects != null && !mJavaObjects.isEmpty()) { + mJsInterfaceHolder.addJavaObjects(mJavaObjects); + } + if (mWebListenerManager != null) { + mWebListenerManager.setDownloader(mWebCreator.getWebView(), null); + mWebListenerManager.setWebChromeClient(mWebCreator.getWebView(), getChromeClient()); + mWebListenerManager.setWebViewClient(mWebCreator.getWebView(), getWebViewClient()); + } + return this; + } + + private WebChromeClient getChromeClient() { + IndicatorController mIndicatorController = + (this.mIndicatorController == null) ? + IndicatorHandler.getInstance().inJectIndicator(mWebCreator.offer()) + : this.mIndicatorController; + + DefaultChromeClient mDefaultChromeClient = + new DefaultChromeClient(this.mActivity, + this.mIndicatorController = mIndicatorController, + null, this.mIVideo = getIVideo(), + this.mPermissionInterceptor, mWebCreator.getWebView()); + + LogUtils.i(TAG, "WebChromeClient:" + this.mWebChromeClient); + MiddlewareWebChromeBase header = this.mMiddlewareWebChromeBaseHeader; + if (this.mWebChromeClient != null) { + this.mWebChromeClient.enq(header); + header = this.mWebChromeClient; + } + if (header != null) { + MiddlewareWebChromeBase tail = header; + int count = 1; + MiddlewareWebChromeBase tmp = header; + for (; tmp.next() != null; ) { + tail = tmp = tmp.next(); + count++; + } + LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count); + tail.setDelegate(mDefaultChromeClient); + return this.mTargetChromeClient = header; + } else { + return this.mTargetChromeClient = mDefaultChromeClient; + } + } + + public enum SecurityType { + DEFAULT_CHECK, STRICT_CHECK; + } + + public static final class AgentBuilder { + private Activity mActivity; + private Fragment mFragment; + private ViewGroup mViewGroup; + private boolean mIsNeedDefaultProgress; + private int mIndex = -1; + private BaseIndicatorView mBaseIndicatorView; + private IndicatorController mIndicatorController = null; + /*默认进度条是显示的*/ + private boolean mEnableIndicator = true; + private ViewGroup.LayoutParams mLayoutParams = null; + private com.just.agentweb.WebViewClient mWebViewClient; + private com.just.agentweb.WebChromeClient mWebChromeClient; + private int mIndicatorColor = -1; + private IAgentWebSettings mAgentWebSettings; + private WebCreator mWebCreator; + private HttpHeaders mHttpHeaders = null; + private IEventHandler mIEventHandler; + private int mHeight = -1; + private ArrayMap mJavaObject; + private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK; + private WebView mWebView; + private boolean mWebClientHelper = true; + private IWebLayout mWebLayout = null; + private PermissionInterceptor mPermissionInterceptor = null; + private AbsAgentWebUIController mAgentWebUIController; + private DefaultWebClient.OpenOtherPageWays mOpenOtherPage = null; + private boolean mIsInterceptUnkownUrl = false; + private MiddlewareWebClientBase mMiddlewareWebClientBaseHeader; + private MiddlewareWebClientBase mMiddlewareWebClientBaseTail; + private MiddlewareWebChromeBase mChromeMiddleWareHeader = null; + private MiddlewareWebChromeBase mChromeMiddleWareTail = null; + private View mErrorView; + private int mErrorLayout; + private int mReloadId; + private int mTag = -1; + + public AgentBuilder(@NonNull Activity activity, @NonNull Fragment fragment) { + mActivity = activity; + mFragment = fragment; + mTag = AgentWeb.FRAGMENT_TAG; + } + + public AgentBuilder(@NonNull Activity activity) { + mActivity = activity; + mTag = AgentWeb.ACTIVITY_TAG; + } + + + public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, @NonNull ViewGroup.LayoutParams lp) { + this.mViewGroup = v; + this.mLayoutParams = lp; + return new IndicatorBuilder(this); + } + + public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, int index, @NonNull ViewGroup.LayoutParams lp) { + this.mViewGroup = v; + this.mLayoutParams = lp; + this.mIndex = index; + return new IndicatorBuilder(this); + } + + private PreAgentWeb buildAgentWeb() { + if (mTag == AgentWeb.FRAGMENT_TAG && this.mViewGroup == null) { + throw new NullPointerException("ViewGroup is null,Please check your parameters ."); + } + return new PreAgentWeb(HookManager.hookAgentWeb(new AgentWeb(this), this)); + } + + private void addJavaObject(String key, Object o) { + if (mJavaObject == null) { + mJavaObject = new ArrayMap<>(); + } + mJavaObject.put(key, o); + } + + private void addHeader(String baseUrl, String k, String v) { + if (mHttpHeaders == null) { + mHttpHeaders = HttpHeaders.create(); + } + mHttpHeaders.additionalHttpHeader(baseUrl, k, v); + } + + private void addHeader(String baseUrl, Map headers) { + if (mHttpHeaders == null) { + mHttpHeaders = HttpHeaders.create(); + } + mHttpHeaders.additionalHttpHeaders(baseUrl, headers); + } + } + + public static class IndicatorBuilder { + private AgentBuilder mAgentBuilder = null; + + public IndicatorBuilder(AgentBuilder agentBuilder) { + this.mAgentBuilder = agentBuilder; + } + + public CommonBuilder useDefaultIndicator(int color) { + this.mAgentBuilder.mEnableIndicator = true; + this.mAgentBuilder.mIndicatorColor = color; + return new CommonBuilder(mAgentBuilder); + } + + public CommonBuilder useDefaultIndicator() { + this.mAgentBuilder.mEnableIndicator = true; + return new CommonBuilder(mAgentBuilder); + } + + public CommonBuilder closeIndicator() { + this.mAgentBuilder.mEnableIndicator = false; + this.mAgentBuilder.mIndicatorColor = -1; + this.mAgentBuilder.mHeight = -1; + return new CommonBuilder(mAgentBuilder); + } + + public CommonBuilder setCustomIndicator(@NonNull BaseIndicatorView v) { + if (v != null) { + this.mAgentBuilder.mEnableIndicator = true; + this.mAgentBuilder.mBaseIndicatorView = v; + this.mAgentBuilder.mIsNeedDefaultProgress = false; + } else { + this.mAgentBuilder.mEnableIndicator = true; + this.mAgentBuilder.mIsNeedDefaultProgress = true; + } + return new CommonBuilder(mAgentBuilder); + } + + public CommonBuilder useDefaultIndicator(@ColorInt int color, int height_dp) { + this.mAgentBuilder.mIndicatorColor = color; + this.mAgentBuilder.mHeight = height_dp; + return new CommonBuilder(this.mAgentBuilder); + } + } + + public static class CommonBuilder { + private AgentBuilder mAgentBuilder; + + public CommonBuilder(AgentBuilder agentBuilder) { + this.mAgentBuilder = agentBuilder; + } + + public CommonBuilder setEventHanadler(@Nullable IEventHandler iEventHandler) { + mAgentBuilder.mIEventHandler = iEventHandler; + return this; + } + + public CommonBuilder closeWebViewClientHelper() { + mAgentBuilder.mWebClientHelper = false; + return this; + } + + public CommonBuilder setWebChromeClient(@Nullable com.just.agentweb.WebChromeClient webChromeClient) { + this.mAgentBuilder.mWebChromeClient = webChromeClient; + return this; + } + + public CommonBuilder setWebViewClient(@Nullable com.just.agentweb.WebViewClient webChromeClient) { + this.mAgentBuilder.mWebViewClient = webChromeClient; + return this; + } + + public CommonBuilder useMiddlewareWebClient(@NonNull MiddlewareWebClientBase middleWrareWebClientBase) { + if (middleWrareWebClientBase == null) { + return this; + } + if (this.mAgentBuilder.mMiddlewareWebClientBaseHeader == null) { + this.mAgentBuilder.mMiddlewareWebClientBaseHeader = this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase; + } else { + this.mAgentBuilder.mMiddlewareWebClientBaseTail.enq(middleWrareWebClientBase); + this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase; + } + return this; + } + + public CommonBuilder useMiddlewareWebChrome(@NonNull MiddlewareWebChromeBase middlewareWebChromeBase) { + if (middlewareWebChromeBase == null) { + return this; + } + if (this.mAgentBuilder.mChromeMiddleWareHeader == null) { + this.mAgentBuilder.mChromeMiddleWareHeader = this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase; + } else { + this.mAgentBuilder.mChromeMiddleWareTail.enq(middlewareWebChromeBase); + this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase; + } + return this; + } + + public CommonBuilder setMainFrameErrorView(@NonNull View view) { + this.mAgentBuilder.mErrorView = view; + return this; + } + + public CommonBuilder setMainFrameErrorView(@LayoutRes int errorLayout, @IdRes int clickViewId) { + this.mAgentBuilder.mErrorLayout = errorLayout; + this.mAgentBuilder.mReloadId = clickViewId; + return this; + } + + public CommonBuilder setAgentWebWebSettings(@Nullable IAgentWebSettings agentWebSettings) { + this.mAgentBuilder.mAgentWebSettings = agentWebSettings; + return this; + } + + public PreAgentWeb createAgentWeb() { + return this.mAgentBuilder.buildAgentWeb(); + } + + + public CommonBuilder addJavascriptInterface(@NonNull String name, @NonNull Object o) { + this.mAgentBuilder.addJavaObject(name, o); + return this; + } + + public CommonBuilder setSecurityType(@NonNull SecurityType type) { + this.mAgentBuilder.mSecurityType = type; + return this; + } + + public CommonBuilder setWebView(@Nullable WebView webView) { + this.mAgentBuilder.mWebView = webView; + return this; + } + + public CommonBuilder setWebLayout(@Nullable IWebLayout iWebLayout) { + this.mAgentBuilder.mWebLayout = iWebLayout; + return this; + } + + public CommonBuilder additionalHttpHeader(String baseUrl, String k, String v) { + this.mAgentBuilder.addHeader(baseUrl, k, v); + return this; + } + + public CommonBuilder additionalHttpHeader(String baseUrl, Map headers) { + this.mAgentBuilder.addHeader(baseUrl, headers); + return this; + } + + public CommonBuilder setPermissionInterceptor(@Nullable PermissionInterceptor permissionInterceptor) { + this.mAgentBuilder.mPermissionInterceptor = permissionInterceptor; + return this; + } + + public CommonBuilder setAgentWebUIController(@Nullable AgentWebUIControllerImplBase agentWebUIController) { + this.mAgentBuilder.mAgentWebUIController = agentWebUIController; + return this; + } + + public CommonBuilder setOpenOtherPageWays(@Nullable DefaultWebClient.OpenOtherPageWays openOtherPageWays) { + this.mAgentBuilder.mOpenOtherPage = openOtherPageWays; + return this; + } + + public CommonBuilder interceptUnkownUrl() { + this.mAgentBuilder.mIsInterceptUnkownUrl = true; + return this; + } + } + + private static final class PermissionInterceptorWrapper implements PermissionInterceptor { + + private WeakReference mWeakReference; + + private PermissionInterceptorWrapper(PermissionInterceptor permissionInterceptor) { + this.mWeakReference = new WeakReference(permissionInterceptor); + } + + @Override + public boolean intercept(String url, String[] permissions, String a) { + if (this.mWeakReference.get() == null) { + return false; + } + return mWeakReference.get().intercept(url, permissions, a); + } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebConfig.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebConfig.java index 231d4ab..a309fa8 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebConfig.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebConfig.java @@ -19,234 +19,212 @@ import android.content.Context; import android.os.AsyncTask; import android.os.Build; -import android.support.annotation.Nullable; import android.text.TextUtils; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.ValueCallback; import android.webkit.WebView; +import androidx.annotation.Nullable; + import java.io.File; import static com.just.agentweb.AgentWebUtils.getAgentWebFilePath; - /** - * @author cenxiaozhong * @since 1.0.0 + * @author cenxiaozhong */ public class AgentWebConfig { - - static final String FILE_CACHE_PATH = "agentweb-cache"; - static final String AGENTWEB_CACHE_PATCH = File.separator + "agentweb-cache"; - /** - * 缓存路径 - */ - static String AGENTWEB_FILE_PATH; - /** - * DEBUG 模式 , 如果需要查看日志请设置为 true - */ - public static boolean DEBUG = false; - /** - * 当前操作系统是否低于 KITKAT - */ - static final boolean IS_KITKAT_OR_BELOW_KITKAT = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; - /** - * 默认 WebView 类型 。 - */ - public static final int WEBVIEW_DEFAULT_TYPE = 1; - /** - * 使用 AgentWebView - */ - public static final int WEBVIEW_AGENTWEB_SAFE_TYPE = 2; - /** - * 自定义 WebView - */ - public static final int WEBVIEW_CUSTOM_TYPE = 3; - static int WEBVIEW_TYPE = WEBVIEW_DEFAULT_TYPE; - private static volatile boolean IS_INITIALIZED = false; - private static final String TAG = AgentWebConfig.class.getSimpleName(); - /** - * AgentWeb 的版本 - */ - public static final String AGENTWEB_VERSION = " agentweb/4.0.2 "; - - public static final String AGENTWEB_NAME = "AgentWeb"; - /** - * 通过JS获取的文件大小, 这里限制最大为5MB ,太大会抛出 OutOfMemoryError - */ - public static int MAX_FILE_LENGTH = 1024 * 1024 * 5; - - - //获取Cookie - public static String getCookiesByUrl(String url) { - return CookieManager.getInstance() == null ? null : CookieManager.getInstance().getCookie(url); - } - - public static void debug() { - DEBUG = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - WebView.setWebContentsDebuggingEnabled(true); - } - } - - /** - * 删除所有已经过期的 Cookies - */ - public static void removeExpiredCookies() { - CookieManager mCookieManager = null; - if ((mCookieManager = CookieManager.getInstance()) != null) { //同步清除 - mCookieManager.removeExpiredCookie(); - toSyncCookies(); - } - } - - /** - * 删除所有 Cookies - */ - public static void removeAllCookies() { - removeAllCookies(null); - } - - // 解决兼容 Android 4.4 java.lang.NoSuchMethodError: android.webkit.CookieManager.removeSessionCookies - public static void removeSessionCookies() { - removeSessionCookies(null); - } - - /** - * 同步cookie - * - * @param url - * @param cookies - */ - public static void syncCookie(String url, String cookies) { - - CookieManager mCookieManager = CookieManager.getInstance(); - if (mCookieManager != null) { - mCookieManager.setCookie(url, cookies); - toSyncCookies(); - } - } - - public static void removeSessionCookies(ValueCallback callback) { - - if (callback == null) { - callback = getDefaultIgnoreCallback(); - } - if (CookieManager.getInstance() == null) { - callback.onReceiveValue(new Boolean(false)); - return; - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - CookieManager.getInstance().removeSessionCookie(); - toSyncCookies(); - callback.onReceiveValue(new Boolean(true)); - return; - } - CookieManager.getInstance().removeSessionCookies(callback); - toSyncCookies(); - - } - - /** - * @param context - * @return WebView 的缓存路径 - */ - public static String getCachePath(Context context) { - return context.getCacheDir().getAbsolutePath() + AGENTWEB_CACHE_PATCH; - } - - /** - * @param context - * @return AgentWeb 缓存路径 - */ - public static String getExternalCachePath(Context context) { - return getAgentWebFilePath(context); - } - - - //Android 4.4 NoSuchMethodError: android.webkit.CookieManager.removeAllCookies - public static void removeAllCookies(@Nullable ValueCallback callback) { - - if (callback == null) { - callback = getDefaultIgnoreCallback(); - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - CookieManager.getInstance().removeAllCookie(); - toSyncCookies(); - callback.onReceiveValue(!CookieManager.getInstance().hasCookies()); - return; - } - CookieManager.getInstance().removeAllCookies(callback); - toSyncCookies(); - } - - /** - * 清空缓存 - * - * @param context - */ - public static synchronized void clearDiskCache(Context context) { - - try { - - AgentWebUtils.clearCacheFolder(new File(getCachePath(context)), 0); - String path = getExternalCachePath(context); - if (!TextUtils.isEmpty(path)) { - File mFile = new File(path); - AgentWebUtils.clearCacheFolder(mFile, 0); - } - } catch (Throwable throwable) { - if (LogUtils.isDebug()) { - throwable.printStackTrace(); - } - } - - } - - - static synchronized void initCookiesManager(Context context) { - if (!IS_INITIALIZED) { - createCookiesSyncInstance(context); - IS_INITIALIZED = true; - } - } - - private static void createCookiesSyncInstance(Context context) { - - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - CookieSyncManager.createInstance(context); - } - } - - private static void toSyncCookies() { - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - CookieSyncManager.getInstance().sync(); - return; - } - AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - - CookieManager.getInstance().flush(); - - } - }); - } - - - static String getDatabasesCachePath(Context context) { - return context.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); - } - - private static ValueCallback getDefaultIgnoreCallback() { - return new ValueCallback() { - @Override - public void onReceiveValue(Boolean ignore) { - LogUtils.i(TAG, "removeExpiredCookies:" + ignore); - } - }; - } + static final String FILE_CACHE_PATH = "agentweb-cache"; + static final String AGENTWEB_CACHE_PATCH = File.separator + "agentweb-cache"; + /** + * 缓存路径 + */ + static String AGENTWEB_FILE_PATH; + /** + * DEBUG 模式 , 如果需要查看日志请设置为 true + */ + public static boolean DEBUG = false; + /** + * 当前操作系统是否低于 KITKAT + */ + static final boolean IS_KITKAT_OR_BELOW_KITKAT = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; + /** + * 默认 WebView 类型 。 + */ + public static final int WEBVIEW_DEFAULT_TYPE = 1; + /** + * 使用 AgentWebView + */ + public static final int WEBVIEW_AGENTWEB_SAFE_TYPE = 2; + /** + * 自定义 WebView + */ + public static final int WEBVIEW_CUSTOM_TYPE = 3; + static int WEBVIEW_TYPE = WEBVIEW_DEFAULT_TYPE; + private static volatile boolean IS_INITIALIZED = false; + private static final String TAG = AgentWebConfig.class.getSimpleName(); + /** + * AgentWeb 的版本 + */ + public static final String AGENTWEB_VERSION = " agentweb/4.0.2 "; + public static final String AGENTWEB_NAME="AgentWeb"; + /** + * 通过JS获取的文件大小, 这里限制最大为5MB ,太大会抛出 OutOfMemoryError + */ + public static int MAX_FILE_LENGTH = 1024 * 1024 * 5; + //获取Cookie + public static String getCookiesByUrl(String url) { + return CookieManager.getInstance() == null ? null : CookieManager.getInstance().getCookie(url); + } + + public static void debug() { + DEBUG = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + WebView.setWebContentsDebuggingEnabled(true); + } + } + /** + * 删除所有已经过期的 Cookies + */ + public static void removeExpiredCookies() { + CookieManager mCookieManager = null; + if ((mCookieManager = CookieManager.getInstance()) != null) { //同步清除 + mCookieManager.removeExpiredCookie(); + toSyncCookies(); + } + } + /** + * 删除所有 Cookies + */ + public static void removeAllCookies() { + removeAllCookies(null); + } + + // 解决兼容 Android 4.4 java.lang.NoSuchMethodError: android.webkit.CookieManager.removeSessionCookies + public static void removeSessionCookies() { + removeSessionCookies(null); + } + /** + * 同步cookie + * + * @param url + * @param cookies + */ + public static void syncCookie(String url, String cookies) { + CookieManager mCookieManager = CookieManager.getInstance(); + if (mCookieManager != null) { + mCookieManager.setCookie(url, cookies); + toSyncCookies(); + } + } + + public static void removeSessionCookies(ValueCallback callback) { + if (callback == null) { + callback = getDefaultIgnoreCallback(); + } + if (CookieManager.getInstance() == null) { + callback.onReceiveValue(new Boolean(false)); + return; + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeSessionCookie(); + toSyncCookies(); + callback.onReceiveValue(new Boolean(true)); + return; + } + CookieManager.getInstance().removeSessionCookies(callback); + toSyncCookies(); + } + /** + * @param context + * @return WebView 的缓存路径 + */ + public static String getCachePath(Context context) { + return context.getCacheDir().getAbsolutePath() + AGENTWEB_CACHE_PATCH; + } + /** + * @param context + * @return AgentWeb 缓存路径 + */ + public static String getExternalCachePath(Context context) { + return getAgentWebFilePath(context); + } + + + //Android 4.4 NoSuchMethodError: android.webkit.CookieManager.removeAllCookies + public static void removeAllCookies(@Nullable ValueCallback callback) { + if (callback == null) { + callback = getDefaultIgnoreCallback(); + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeAllCookie(); + toSyncCookies(); + callback.onReceiveValue(!CookieManager.getInstance().hasCookies()); + return; + } + CookieManager.getInstance().removeAllCookies(callback); + toSyncCookies(); + } + + /** + * 清空缓存 + * + * @param context + */ + public static synchronized void clearDiskCache(Context context) { + try { + AgentWebUtils.clearCacheFolder(new File(getCachePath(context)), 0); + String path = getExternalCachePath(context); + if (!TextUtils.isEmpty(path)) { + File mFile = new File(path); + AgentWebUtils.clearCacheFolder(mFile, 0); + } + } catch (Throwable throwable) { + if (LogUtils.isDebug()) { + throwable.printStackTrace(); + } + } + } + + + static synchronized void initCookiesManager(Context context) { + if (!IS_INITIALIZED) { + createCookiesSyncInstance(context); + IS_INITIALIZED = true; + } + } + + private static void createCookiesSyncInstance(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.createInstance(context); + } + } + + private static void toSyncCookies() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.getInstance().sync(); + return; + } + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + CookieManager.getInstance().flush(); + } + }); + } + + static String getDatabasesCachePath(Context context) { + return context.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); + } + + private static ValueCallback getDefaultIgnoreCallback() { + return new ValueCallback() { + @Override + public void onReceiveValue(Boolean ignore) { + LogUtils.i(TAG, "removeExpiredCookies:" + ignore); + } + }; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebFileProvider.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebFileProvider.java index 55c0e8c..33a4114 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebFileProvider.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebFileProvider.java @@ -16,11 +16,20 @@ package com.just.agentweb; -import android.support.v4.content.FileProvider; +import android.content.Context; +import android.content.pm.ProviderInfo; + +import androidx.annotation.NonNull; +import androidx.core.content.FileProvider; /** - * @author cenxiaozhong * @since 2.0.0 + * @author cenxiaozhong */ public class AgentWebFileProvider extends FileProvider { + + @Override + public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) { + super.attachInfo(context, info); + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebJsInterfaceCompat.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebJsInterfaceCompat.java index 1c37fdc..f3fc6fa 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebJsInterfaceCompat.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebJsInterfaceCompat.java @@ -29,48 +29,43 @@ */ public class AgentWebJsInterfaceCompat { - private WeakReference mReference = null; - private WeakReference mActivityWeakReference = null; - private String TAG = this.getClass().getSimpleName(); + private WeakReference mReference = null; + private WeakReference mActivityWeakReference = null; + private String TAG = this.getClass().getSimpleName(); - AgentWebJsInterfaceCompat(AgentWeb agentWeb, Activity activity) { - mReference = new WeakReference(agentWeb); - mActivityWeakReference = new WeakReference(activity); - } + AgentWebJsInterfaceCompat(AgentWeb agentWeb, Activity activity) { + mReference = new WeakReference(agentWeb); + mActivityWeakReference = new WeakReference(activity); + } + @JavascriptInterface + public void uploadFile() { + uploadFile("*/*"); + } - @JavascriptInterface - public void uploadFile() { - uploadFile("*/*"); - } - - @JavascriptInterface - public void uploadFile(String acceptType) { - LogUtils.i(TAG, acceptType + " " + mActivityWeakReference.get() + " " + mReference.get()); - if (mActivityWeakReference.get() != null && mReference.get() != null) { - - AgentWebUtils.showFileChooserCompat(mActivityWeakReference.get(), - mReference.get().getWebCreator().getWebView(), - null, - null, - mReference.get().getPermissionInterceptor(), - null, - acceptType, - new Handler.Callback() { - @Override - public boolean handleMessage(Message msg) { - if (mReference.get() != null) { - mReference.get().getJsAccessEntrace() - .quickCallJs("uploadFileResult", - msg.obj instanceof String ? (String) msg.obj : null); - } - return true; - } - } - ); - - - } - } - + @JavascriptInterface + public void uploadFile(String acceptType) { + LogUtils.i(TAG, acceptType + " " + mActivityWeakReference.get() + " " + mReference.get()); + if (mActivityWeakReference.get() != null && mReference.get() != null) { + AgentWebUtils.showFileChooserCompat(mActivityWeakReference.get(), + mReference.get().getWebCreator().getWebView(), + null, + null, + mReference.get().getPermissionInterceptor(), + null, + acceptType, + new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + if (mReference.get() != null) { + mReference.get().getJsAccessEntrace() + .quickCallJs("uploadFileResult", + msg.obj instanceof String ? (String) msg.obj : null); + } + return true; + } + } + ); + } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebPermissions.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebPermissions.java index cc09f72..494851b 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebPermissions.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebPermissions.java @@ -24,32 +24,20 @@ * @since 1.0.0 */ public class AgentWebPermissions { - - - public static final String[] CAMERA; - public static final String[] LOCATION; - public static final String[] STORAGE; - - public static final String ACTION_CAMERA = "Camera"; - public static final String ACTION_LOCATION = "Location"; - public static final String ACTION_STORAGE = "Storage"; - - static { - - - CAMERA = new String[]{ - Manifest.permission.CAMERA}; - - - LOCATION = new String[]{ - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION}; - - - STORAGE = new String[]{ - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE}; - } - - + public static final String[] CAMERA; + public static final String[] LOCATION; + public static final String[] STORAGE; + public static final String ACTION_CAMERA = "Camera"; + public static final String ACTION_LOCATION = "Location"; + public static final String ACTION_STORAGE = "Storage"; + static { + CAMERA = new String[]{ + Manifest.permission.CAMERA}; + LOCATION = new String[]{ + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}; + STORAGE = new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebSettingsImpl.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebSettingsImpl.java index cabcd78..5ce3e05 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebSettingsImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebSettingsImpl.java @@ -17,6 +17,8 @@ package com.just.agentweb; import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; import android.webkit.DownloadListener; import android.webkit.WebView; @@ -33,26 +35,33 @@ protected void bindAgentWebSupport(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; } - @Override public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) { - Class clazz = null; - Object mDefaultDownloadImpl$Extra = null; - try { - clazz = Class.forName("com.just.agentweb.download.DefaultDownloadImpl"); - mDefaultDownloadImpl$Extra = - clazz.getDeclaredMethod("create", Activity.class, WebView.class, - Class.forName("com.just.agentweb.download.DownloadListener"), - Class.forName("com.just.agentweb.download.DownloadingListener"), - PermissionInterceptor.class) - .invoke(mDefaultDownloadImpl$Extra, webView.getContext() - , webView, null, null, mAgentWeb.getPermissionInterceptor()); - - } catch (Throwable ignore) { - if (LogUtils.isDebug()) { - ignore.printStackTrace(); + // Fix Android 5.1 crashing: + // ClassCastException: android.app.ContextImpl cannot be cast to android.app.Activity + if (downloadListener == null) { + Activity activity = getActivityByContext(webView.getContext()); + downloadListener = DefaultDownloadImpl.create(activity, webView, mAgentWeb.getPermissionInterceptor()); + } + return super.setDownloader(webView, downloadListener); + } + + /** + * Copy from com.blankj.utilcode.util.ActivityUtils#getActivityByView + */ + private Activity getActivityByContext(Context context) { + if (context instanceof Activity) return (Activity) context; + while (context instanceof ContextWrapper) { + if (context instanceof Activity) { + return (Activity) context; } + context = ((ContextWrapper) context).getBaseContext(); } - return super.setDownloader(webView, mDefaultDownloadImpl$Extra == null ? downloadListener : (DownloadListener) mDefaultDownloadImpl$Extra); + + + LogUtils.e( "获取 B :", context + "" ); + System.out.println("输出:" + context + ""); + return null; } + } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebUIControllerImplBase.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebUIControllerImplBase.java index 0796186..cd9a73b 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebUIControllerImplBase.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebUIControllerImplBase.java @@ -30,76 +30,75 @@ */ public class AgentWebUIControllerImplBase extends AbsAgentWebUIController { - - public static AbsAgentWebUIController build() { - return new AgentWebUIControllerImplBase(); - } - - @Override - public void onJsAlert(WebView view, String url, String message) { - getDelegate().onJsAlert(view, url, message); - } - - @Override - public void onOpenPagePrompt(WebView view, String url, Handler.Callback callback) { - getDelegate().onOpenPagePrompt(view, url, callback); - } - - @Override - public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { - getDelegate().onJsConfirm(view, url, message, jsResult); - } - - @Override - public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) { - getDelegate().onSelectItemsPrompt(view, url, ways, callback); - } - - @Override - public void onForceDownloadAlert(String url, Handler.Callback callback) { - getDelegate().onForceDownloadAlert(url, callback); - } - - @Override - public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { - getDelegate().onJsPrompt(view, url, message, defaultValue, jsPromptResult); - } - - @Override - public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { - getDelegate().onMainFrameError(view, errorCode, description, failingUrl); - } - - @Override - public void onShowMainFrame() { - getDelegate().onShowMainFrame(); - } - - @Override - public void onLoading(String msg) { - getDelegate().onLoading(msg); - } - - @Override - public void onCancelLoading() { - getDelegate().onCancelLoading(); - } - - - @Override - public void onShowMessage(String message, String from) { - getDelegate().onShowMessage(message, from); - } - - @Override - public void onPermissionsDeny(String[] permissions, String permissionType, String action) { - getDelegate().onPermissionsDeny(permissions, permissionType, action); - } - - @Override - protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { - getDelegate().bindSupportWebParent(webParentLayout, activity); - } + public static AbsAgentWebUIController build() { + return new AgentWebUIControllerImplBase(); + } + + @Override + public void onJsAlert(WebView view, String url, String message) { + getDelegate().onJsAlert(view, url, message); + } + + @Override + public void onOpenPagePrompt(WebView view, String url, Handler.Callback callback) { + getDelegate().onOpenPagePrompt(view, url, callback); + } + + @Override + public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { + getDelegate().onJsConfirm(view, url, message, jsResult); + } + + @Override + public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) { + getDelegate().onSelectItemsPrompt(view, url, ways, callback); + } + + @Override + public void onForceDownloadAlert(String url, Handler.Callback callback) { + getDelegate().onForceDownloadAlert(url, callback); + } + + @Override + public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { + getDelegate().onJsPrompt(view, url, message, defaultValue, jsPromptResult); + } + + @Override + public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { + getDelegate().onMainFrameError(view, errorCode, description, failingUrl); + } + + @Override + public void onShowMainFrame() { + getDelegate().onShowMainFrame(); + } + + @Override + public void onLoading(String msg) { + getDelegate().onLoading(msg); + } + + @Override + public void onCancelLoading() { + getDelegate().onCancelLoading(); + } + + + @Override + public void onShowMessage(String message, String from) { + getDelegate().onShowMessage(message, from); + } + + @Override + public void onPermissionsDeny(String[] permissions, String permissionType, String action) { + getDelegate().onPermissionsDeny(permissions, permissionType, action); + } + + @Override + protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { + getDelegate().bindSupportWebParent(webParentLayout, activity); + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebUtils.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebUtils.java index 67fa891..a062d54 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebUtils.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebUtils.java @@ -36,14 +36,6 @@ import android.os.StatFs; import android.provider.DocumentsContract; import android.provider.MediaStore; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v4.app.AppOpsManagerCompat; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.FileProvider; -import android.support.v4.os.EnvironmentCompat; import android.telephony.TelephonyManager; import android.text.SpannableString; import android.text.Spanned; @@ -60,6 +52,16 @@ import android.webkit.WebView; import android.widget.Toast; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.core.app.AppOpsManagerCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.core.os.EnvironmentCompat; +import androidx.loader.content.CursorLoader; + +import com.google.android.material.snackbar.Snackbar; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -90,872 +92,832 @@ */ public class AgentWebUtils { - private static final String TAG = AgentWebUtils.class.getSimpleName(); - private static Handler mHandler = null; - - private AgentWebUtils() { - throw new UnsupportedOperationException("u can't init me"); - } - - public static int dp2px(Context context, float dipValue) { - - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (dipValue * scale + 0.5f); - } - - - static final void clearWebView(WebView m) { - - if (m == null) { - return; - } - if (Looper.myLooper() != Looper.getMainLooper()) { - return; - } - m.loadUrl("about:blank"); - m.stopLoading(); - if (m.getHandler() != null) { - m.getHandler().removeCallbacksAndMessages(null); - } - m.removeAllViews(); - ViewGroup mViewGroup = null; - if ((mViewGroup = ((ViewGroup) m.getParent())) != null) { - mViewGroup.removeView(m); - } - m.setWebChromeClient(null); - m.setWebViewClient(null); - m.setTag(null); - m.clearHistory(); - m.destroy(); - m = null; - - - } - - static String getAgentWebFilePath(Context context) { - if (!TextUtils.isEmpty(AGENTWEB_FILE_PATH)) { - return AGENTWEB_FILE_PATH; - } - String dir = getDiskExternalCacheDir(context); - File mFile = new File(dir, FILE_CACHE_PATH); - try { - if (!mFile.exists()) { - mFile.mkdirs(); - } - } catch (Throwable throwable) { - LogUtils.i(TAG, "create dir exception"); - } - LogUtils.i(TAG, "path:" + mFile.getAbsolutePath() + " path:" + mFile.getPath()); - return AGENTWEB_FILE_PATH = mFile.getAbsolutePath(); - - } - - - public static File createFileByName(Context context, String name, boolean cover) throws IOException { - - String path = getAgentWebFilePath(context); - if (TextUtils.isEmpty(path)) { - return null; - } - File mFile = new File(path, name); - if (mFile.exists()) { - if (cover) { - mFile.delete(); - mFile.createNewFile(); - } - } else { - mFile.createNewFile(); - } - - return mFile; - } - - public static int checkNetworkType(Context context) { - - int netType = 0; - //连接管理对象 - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - //获取NetworkInfo对象 - @SuppressLint("MissingPermission") NetworkInfo networkInfo = manager.getActiveNetworkInfo(); - if (networkInfo == null) { - return netType; - } - switch (networkInfo.getType()) { - case ConnectivityManager.TYPE_WIFI: - case ConnectivityManager.TYPE_WIMAX: - case ConnectivityManager.TYPE_ETHERNET: - return 1; - - case ConnectivityManager.TYPE_MOBILE: - switch (networkInfo.getSubtype()) { - case TelephonyManager.NETWORK_TYPE_LTE: // 4G - case TelephonyManager.NETWORK_TYPE_HSPAP: - case TelephonyManager.NETWORK_TYPE_EHRPD: - return 2; - case TelephonyManager.NETWORK_TYPE_UMTS: // 3G - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - return 3; - - case TelephonyManager.NETWORK_TYPE_GPRS: // 2G - case TelephonyManager.NETWORK_TYPE_EDGE: - return 4; - - default: - return netType; - } - - default: - - return netType; - } - - } - - public static long getAvailableStorage() { - try { - StatFs stat = new StatFs(Environment.getExternalStorageDirectory().toString()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - return stat.getAvailableBlocksLong() * stat.getBlockSizeLong(); - } else { - return (long) stat.getAvailableBlocks() * (long) stat.getBlockSize(); - } - } catch (RuntimeException ex) { - return 0; - } - } - - - static Uri getUriFromFile(Context context, File file) { - Uri uri = null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - uri = getUriFromFileForN(context, file); - } else { - uri = Uri.fromFile(file); - } - return uri; - } - - static Uri getUriFromFileForN(Context context, File file) { - Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".AgentWebFileProvider", file); - return fileUri; - } - - - static void setIntentDataAndType(Context context, - Intent intent, - String type, - File file, - boolean writeAble) { - if (Build.VERSION.SDK_INT >= 24) { - intent.setDataAndType(getUriFromFile(context, file), type); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (writeAble) { - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - } - } else { - intent.setDataAndType(Uri.fromFile(file), type); - } - } - - - static void setIntentData(Context context, - Intent intent, - File file, - boolean writeAble) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - intent.setData(getUriFromFile(context, file)); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (writeAble) { - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - } - } else { - intent.setData(Uri.fromFile(file)); - } - } - - static String getDiskExternalCacheDir(Context context) { - - File mFile = context.getExternalCacheDir(); - if (Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(mFile))) { - return mFile.getAbsolutePath(); - } - return null; - } - - static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) { - - int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION; - if (writeAble) { - flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - } - intent.addFlags(flag); - List resInfoList = context.getPackageManager() - .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - context.grantUriPermission(packageName, uri, flag); - } - } - - - private static String getMIMEType(File f) { - String type = ""; - String fName = f.getName(); - /* 取得扩展名 */ - String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase(); - - /* 依扩展名的类型决定MimeType */ - if (end.equals("pdf")) { - type = "application/pdf";// - } else if (end.equals("m4a") || end.equals("mp3") || end.equals("mid") || - end.equals("xmf") || end.equals("ogg") || end.equals("wav")) { - type = "audio/*"; - } else if (end.equals("3gp") || end.equals("mp4")) { - type = "video/*"; - } else if (end.equals("jpg") || end.equals("gif") || end.equals("png") || - end.equals("jpeg") || end.equals("bmp")) { - type = "image/*"; - } else if (end.equals("apk")) { - type = "application/vnd.android.package-archive"; - } else if (end.equals("pptx") || end.equals("ppt")) { - type = "application/vnd.ms-powerpoint"; - } else if (end.equals("docx") || end.equals("doc")) { - type = "application/vnd.ms-word"; - } else if (end.equals("xlsx") || end.equals("xls")) { - type = "application/vnd.ms-excel"; - } else { - type = "*/*"; - } - return type; - } - - - private static WeakReference snackbarWeakReference; - - static void show(View parent, - CharSequence text, - int duration, - @ColorInt int textColor, - @ColorInt int bgColor, - CharSequence actionText, - @ColorInt int actionTextColor, - View.OnClickListener listener) { - SpannableString spannableString = new SpannableString(text); - ForegroundColorSpan colorSpan = new ForegroundColorSpan(textColor); - spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - snackbarWeakReference = new WeakReference<>(Snackbar.make(parent, spannableString, duration)); - Snackbar snackbar = snackbarWeakReference.get(); - View view = snackbar.getView(); - view.setBackgroundColor(bgColor); - if (actionText != null && actionText.length() > 0 && listener != null) { - snackbar.setActionTextColor(actionTextColor); - snackbar.setAction(actionText, listener); - } - snackbar.show(); - - } - - static void dismiss() { - if (snackbarWeakReference != null && snackbarWeakReference.get() != null) { - snackbarWeakReference.get().dismiss(); - snackbarWeakReference = null; - } - } - - public static boolean checkWifi(Context context) { - ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - return false; - } - @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); - return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI; - } - - public static boolean checkNetwork(Context context) { - ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - return false; - } - @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); - return info != null && info.isConnected(); - } - - static boolean isOverriedMethod(Object currentObject, String methodName, String method, Class... clazzs) { - LogUtils.i(TAG, " methodName:" + methodName + " method:" + method); - boolean tag = false; - if (currentObject == null) { - return tag; - } - try { - Class clazz = currentObject.getClass(); - Method mMethod = clazz.getMethod(methodName, clazzs); - String gStr = mMethod.toGenericString(); - tag = !gStr.contains(method); - } catch (Exception igonre) { - if (LogUtils.isDebug()) { - igonre.printStackTrace(); - } - } - - LogUtils.i(TAG, "isOverriedMethod:" + tag); - return tag; - } - - static Method isExistMethod(Object o, String methodName, Class... clazzs) { - - if (null == o) { - return null; - } - try { - Class clazz = o.getClass(); - Method mMethod = clazz.getDeclaredMethod(methodName, clazzs); - mMethod.setAccessible(true); - return mMethod; - } catch (Throwable ignore) { - if (LogUtils.isDebug()) { - ignore.printStackTrace(); - } - } - return null; - - } - - static void clearAgentWebCache(Context context) { - try { - clearCacheFolder(new File(getAgentWebFilePath(context)), 0); - } catch (Throwable throwable) { - if (LogUtils.isDebug()) { - throwable.printStackTrace(); - } - } - } - - static void clearWebViewAllCache(Context context, WebView webView) { - - try { - - AgentWebConfig.removeAllCookies(null); - webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); - context.deleteDatabase("webviewCache.db"); - context.deleteDatabase("webview.db"); - webView.clearCache(true); - webView.clearHistory(); - webView.clearFormData(); - clearCacheFolder(new File(AgentWebConfig.getCachePath(context)), 0); - - } catch (Exception ignore) { - //ignore.printStackTrace(); - if (AgentWebConfig.DEBUG) { - ignore.printStackTrace(); - } - } - } - - - static void clearWebViewAllCache(Context context) { - - try { - - clearWebViewAllCache(context, new WebView(context.getApplicationContext())); - } catch (Exception e) { - e.printStackTrace(); - } - } - - static int clearCacheFolder(final File dir, final int numDays) { - - int deletedFiles = 0; - if (dir != null) { - Log.i("Info", "dir:" + dir.getAbsolutePath()); - } - if (dir != null && dir.isDirectory()) { - try { - for (File child : dir.listFiles()) { - - //first delete subdirectories recursively - if (child.isDirectory()) { - deletedFiles += clearCacheFolder(child, numDays); - } - - //then delete the files and subdirectories in this dir - //only empty directories can be deleted, so subdirs have been done first - if (child.lastModified() < new Date().getTime() - numDays * DateUtils.DAY_IN_MILLIS) { - Log.i(TAG, "file name:" + child.getName()); - if (child.delete()) { - deletedFiles++; - } - } - } - } catch (Exception e) { - Log.e("Info", String.format("Failed to clean the cache, result %s", e.getMessage())); - } - } - return deletedFiles; - } - - - static void clearCache(final Context context, final int numDays) { - Log.i("Info", String.format("Starting cache prune, deleting files older than %d days", numDays)); - int numDeletedFiles = clearCacheFolder(context.getCacheDir(), numDays); - Log.i("Info", String.format("Cache pruning completed, %d files deleted", numDeletedFiles)); - } - - - public static String[] uriToPath(Activity activity, Uri[] uris) { - - if (activity == null || uris == null || uris.length == 0) { - return null; - } - try { - String[] paths = new String[uris.length]; - int i = 0; - for (Uri mUri : uris) { - paths[i++] = Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 ? getFileAbsolutePath(activity, mUri) : getRealPathBelowVersion(activity, mUri); - - } - return paths; - } catch (Throwable throwable) { - if (LogUtils.isDebug()) { - throwable.printStackTrace(); - } - } - return null; - - } - - private static String getRealPathBelowVersion(Context context, Uri uri) { - String filePath = null; - LogUtils.i(TAG, "method -> getRealPathBelowVersion " + uri + " path:" + uri.getPath() + " getAuthority:" + uri.getAuthority()); - String[] projection = {MediaStore.Images.Media.DATA}; - - CursorLoader loader = new CursorLoader(context, uri, projection, null, - null, null); - Cursor cursor = loader.loadInBackground(); - - if (cursor != null) { - cursor.moveToFirst(); - filePath = cursor.getString(cursor.getColumnIndex(projection[0])); - cursor.close(); - } - if (filePath == null) { - filePath = uri.getPath(); - - } - return filePath; - } - - - static File createImageFile(Context context) { - File mFile = null; - try { - - String timeStamp = - new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()); - String imageName = String.format("aw_%s.jpg", timeStamp); - mFile = createFileByName(context, imageName, true); - } catch (Throwable e) { - - } - return mFile; - } - - - public static void closeIO(Closeable closeable) { - try { - - if (closeable != null) { - closeable.close(); - } - } catch (Exception e) { - - e.printStackTrace(); - } - - } - - - @TargetApi(19) - static String getFileAbsolutePath(Activity context, Uri fileUri) { - - if (context == null || fileUri == null) { - return null; - } - - LogUtils.i(TAG, "getAuthority:" + fileUri.getAuthority() + " getHost:" + fileUri.getHost() + " getPath:" + fileUri.getPath() + " getScheme:" + fileUri.getScheme() + " query:" + fileUri.getQuery()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, fileUri)) { - if (isExternalStorageDocument(fileUri)) { - String docId = DocumentsContract.getDocumentId(fileUri); - String[] split = docId.split(":"); - String type = split[0]; - if ("primary".equalsIgnoreCase(type)) { - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } - } else if (isDownloadsDocument(fileUri)) { - String id = DocumentsContract.getDocumentId(fileUri); - Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - return getDataColumn(context, contentUri, null, null); - } else if (isMediaDocument(fileUri)) { - String docId = DocumentsContract.getDocumentId(fileUri); - String[] split = docId.split(":"); - String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - String selection = MediaStore.Images.Media._ID + "=?"; - String[] selectionArgs = new String[]{split[1]}; - return getDataColumn(context, contentUri, selection, selectionArgs); - } else { - - } - } // MediaStore (and general) - else if (fileUri.getAuthority().equalsIgnoreCase(context.getPackageName() + ".AgentWebFileProvider")) { - - String path = fileUri.getPath(); - int index = path.lastIndexOf("/"); - return getAgentWebFilePath(context) + File.separator + path.substring(index + 1, path.length()); - } else if ("content".equalsIgnoreCase(fileUri.getScheme())) { - // Return the remote address - if (isGooglePhotosUri(fileUri)) { - return fileUri.getLastPathSegment(); - } - return getDataColumn(context, fileUri, null, null); - } - // File - else if ("file".equalsIgnoreCase(fileUri.getScheme())) { - return fileUri.getPath(); - } - return null; - } - - static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { - Cursor cursor = null; - String[] projection = {MediaStore.Images.Media.DATA}; - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - return cursor.getString(index); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is Google Photos. - */ - static boolean isGooglePhotosUri(Uri uri) { - return "com.google.android.apps.photos.content".equals(uri.getAuthority()); - } - - static Intent getInstallApkIntentCompat(Context context, File file) { - - Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW); - setIntentDataAndType(context, mIntent, "application/vnd.android.package-archive", file, false); - return mIntent; - } - - public static Intent getCommonFileIntentCompat(Context context, File file) { - Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW); - setIntentDataAndType(context, mIntent, getMIMEType(file), file, false); - return mIntent; - } - - static Intent getIntentCaptureCompat(Context context, File file) { - Intent mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - Uri mUri = getUriFromFile(context, file); - mIntent.addCategory(Intent.CATEGORY_DEFAULT); - mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); - return mIntent; - } - - - static boolean isJson(String target) { - if (TextUtils.isEmpty(target)) { - return false; - } - - boolean tag = false; - try { - if (target.startsWith("[")) { - new JSONArray(target); - } else { - new JSONObject(target); - } - tag = true; - } catch (JSONException ignore) { + private static final String TAG = AgentWebUtils.class.getSimpleName(); + private static Handler mHandler = null; + + private AgentWebUtils() { + throw new UnsupportedOperationException("u can't init me"); + } + + public static int dp2px(Context context, float dipValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } + + static final void clearWebView(WebView m) { + if (m == null) { + return; + } + if (Looper.myLooper() != Looper.getMainLooper()) { + return; + } + m.loadUrl("about:blank"); + m.stopLoading(); + if (m.getHandler() != null) { + m.getHandler().removeCallbacksAndMessages(null); + } + m.removeAllViews(); + ViewGroup mViewGroup = null; + if ((mViewGroup = ((ViewGroup) m.getParent())) != null) { + mViewGroup.removeView(m); + } + m.setWebChromeClient(null); + m.setWebViewClient(null); + m.setTag(null); + m.clearHistory(); + m.destroy(); + m = null; + } + + public static String getAgentWebFilePath(Context context) { + if (!TextUtils.isEmpty(AGENTWEB_FILE_PATH)) { + return AGENTWEB_FILE_PATH; + } + String dir = getDiskExternalCacheDir(context); + File mFile = new File(dir, FILE_CACHE_PATH); + try { + if (!mFile.exists()) { + mFile.mkdirs(); + } + } catch (Throwable throwable) { + LogUtils.i(TAG, "create dir exception"); + } + LogUtils.i(TAG, "path:" + mFile.getAbsolutePath() + " path:" + mFile.getPath()); + return AGENTWEB_FILE_PATH = mFile.getAbsolutePath(); + } + + + public static File createFileByName(Context context, String name, boolean cover) throws IOException { + String path = getAgentWebFilePath(context); + if (TextUtils.isEmpty(path)) { + return null; + } + File mFile = new File(path, name); + if (mFile.exists()) { + if (cover) { + mFile.delete(); + mFile.createNewFile(); + } + } else { + mFile.createNewFile(); + } + return mFile; + } + + public static int checkNetworkType(Context context) { + int netType = 0; + //连接管理对象 + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + //获取NetworkInfo对象 + @SuppressLint("MissingPermission") NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo == null) { + return netType; + } + switch (networkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + case ConnectivityManager.TYPE_WIMAX: + case ConnectivityManager.TYPE_ETHERNET: + return 1; + case ConnectivityManager.TYPE_MOBILE: + switch (networkInfo.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_LTE: // 4G + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_EHRPD: + return 2; + case TelephonyManager.NETWORK_TYPE_UMTS: // 3G + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + return 3; + case TelephonyManager.NETWORK_TYPE_GPRS: // 2G + case TelephonyManager.NETWORK_TYPE_EDGE: + return 4; + default: + return netType; + } + + default: + return netType; + } + } + + public static long getAvailableStorage() { + try { + StatFs stat = new StatFs(Environment.getExternalStorageDirectory().toString()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return stat.getAvailableBlocksLong() * stat.getBlockSizeLong(); + } else { + return (long) stat.getAvailableBlocks() * (long) stat.getBlockSize(); + } + } catch (RuntimeException ex) { + return 0; + } + } + + + static Uri getUriFromFile(Context context, File file) { + Uri uri = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + uri = getUriFromFileForN(context, file); + } else { + uri = Uri.fromFile(file); + } + return uri; + } + + static Uri getUriFromFileForN(Context context, File file) { + Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".AgentWebFileProvider", file); + return fileUri; + } + + + static void setIntentDataAndType(Context context, + Intent intent, + String type, + File file, + boolean writeAble) { + if (Build.VERSION.SDK_INT >= 24) { + intent.setDataAndType(getUriFromFile(context, file), type); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (writeAble) { + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + } else { + intent.setDataAndType(Uri.fromFile(file), type); + } + } + + + static void setIntentData(Context context, + Intent intent, + File file, + boolean writeAble) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.setData(getUriFromFile(context, file)); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (writeAble) { + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + } else { + intent.setData(Uri.fromFile(file)); + } + } + + static String getDiskExternalCacheDir(Context context) { + File mFile = context.getExternalCacheDir(); + if (Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(mFile))) { + return mFile.getAbsolutePath(); + } + return null; + } + + static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) { + int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION; + if (writeAble) { + flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + } + intent.addFlags(flag); + List resInfoList = context.getPackageManager() + .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, uri, flag); + } + } + + + private static String getMIMEType(File f) { + String type = ""; + String fName = f.getName(); + /* 取得扩展名 */ + String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase(); + /* 依扩展名的类型决定MimeType */ + if (end.equals("pdf")) { + type = "application/pdf";// + } else if (end.equals("m4a") || end.equals("mp3") || end.equals("mid") || + end.equals("xmf") || end.equals("ogg") || end.equals("wav")) { + type = "audio/*"; + } else if (end.equals("3gp") || end.equals("mp4")) { + type = "video/*"; + } else if (end.equals("jpg") || end.equals("gif") || end.equals("png") || + end.equals("jpeg") || end.equals("bmp")) { + type = "image/*"; + } else if (end.equals("apk")) { + type = "application/vnd.android.package-archive"; + } else if (end.equals("pptx") || end.equals("ppt")) { + type = "application/vnd.ms-powerpoint"; + } else if (end.equals("docx") || end.equals("doc")) { + type = "application/vnd.ms-word"; + } else if (end.equals("xlsx") || end.equals("xls")) { + type = "application/vnd.ms-excel"; + } else { + type = "*/*"; + } + return type; + } + + + private static WeakReference snackbarWeakReference; + + static void show(View parent, + CharSequence text, + int duration, + @ColorInt int textColor, + @ColorInt int bgColor, + CharSequence actionText, + @ColorInt int actionTextColor, + View.OnClickListener listener) { + SpannableString spannableString = new SpannableString(text); + ForegroundColorSpan colorSpan = new ForegroundColorSpan(textColor); + spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + snackbarWeakReference = new WeakReference<>(Snackbar.make(parent, spannableString, duration)); + Snackbar snackbar = snackbarWeakReference.get(); + View view = snackbar.getView(); + view.setBackgroundColor(bgColor); + if (actionText != null && actionText.length() > 0 && listener != null) { + snackbar.setActionTextColor(actionTextColor); + snackbar.setAction(actionText, listener); + } + snackbar.show(); + } + + static void dismiss() { + if (snackbarWeakReference != null && snackbarWeakReference.get() != null) { + snackbarWeakReference.get().dismiss(); + snackbarWeakReference = null; + } + } + + public static boolean checkWifi(Context context) { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + return false; + } + @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); + return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI; + } + + public static boolean checkNetwork(Context context) { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + return false; + } + @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); + return info != null && info.isConnected(); + } + + static boolean isOverriedMethod(Object currentObject, String methodName, String method, Class... clazzs) { + LogUtils.i(TAG, " methodName:" + methodName + " method:" + method); + boolean tag = false; + if (currentObject == null) { + return tag; + } + try { + Class clazz = currentObject.getClass(); + Method mMethod = clazz.getMethod(methodName, clazzs); + String gStr = mMethod.toGenericString(); + tag = !gStr.contains(method); + } catch (Exception igonre) { + if (LogUtils.isDebug()) { + igonre.printStackTrace(); + } + } + + LogUtils.i(TAG, "isOverriedMethod:" + tag); + return tag; + } + + static Method isExistMethod(Object o, String methodName, Class... clazzs) { + + if (null == o) { + return null; + } + try { + Class clazz = o.getClass(); + Method mMethod = clazz.getDeclaredMethod(methodName, clazzs); + mMethod.setAccessible(true); + return mMethod; + } catch (Throwable ignore) { + if (LogUtils.isDebug()) { + ignore.printStackTrace(); + } + } + return null; + + } + + static void clearAgentWebCache(Context context) { + try { + clearCacheFolder(new File(getAgentWebFilePath(context)), 0); + } catch (Throwable throwable) { + if (LogUtils.isDebug()) { + throwable.printStackTrace(); + } + } + } + + static void clearWebViewAllCache(Context context, WebView webView) { + + try { + + AgentWebConfig.removeAllCookies(null); + webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); + context.deleteDatabase("webviewCache.db"); + context.deleteDatabase("webview.db"); + webView.clearCache(true); + webView.clearHistory(); + webView.clearFormData(); + clearCacheFolder(new File(AgentWebConfig.getCachePath(context)), 0); + + } catch (Exception ignore) { + //ignore.printStackTrace(); + if (AgentWebConfig.DEBUG) { + ignore.printStackTrace(); + } + } + } + + static void clearWebViewAllCache(Context context) { + + try { + + clearWebViewAllCache(context, new LollipopFixedWebView(context.getApplicationContext())); + } catch (Exception e) { + e.printStackTrace(); + } + } + + static int clearCacheFolder(final File dir, final int numDays) { + int deletedFiles = 0; + if (dir != null) { + Log.i("Info", "dir:" + dir.getAbsolutePath()); + } + if (dir != null && dir.isDirectory()) { + try { + for (File child : dir.listFiles()) { + + //first delete subdirectories recursively + if (child.isDirectory()) { + deletedFiles += clearCacheFolder(child, numDays); + } + + //then delete the files and subdirectories in this dir + //only empty directories can be deleted, so subdirs have been done first + if (child.lastModified() < new Date().getTime() - numDays * DateUtils.DAY_IN_MILLIS) { + Log.i(TAG, "file name:" + child.getName()); + if (child.delete()) { + deletedFiles++; + } + } + } + } catch (Exception e) { + Log.e("Info", String.format("Failed to clean the cache, result %s", e.getMessage())); + } + } + return deletedFiles; + } + + + static void clearCache(final Context context, final int numDays) { + Log.i("Info", String.format("Starting cache prune, deleting files older than %d days", numDays)); + int numDeletedFiles = clearCacheFolder(context.getCacheDir(), numDays); + Log.i("Info", String.format("Cache pruning completed, %d files deleted", numDeletedFiles)); + } + + public static String[] uriToPath(Activity activity, Uri[] uris) { + if (activity == null || uris == null || uris.length == 0) { + return null; + } + try { + String[] paths = new String[uris.length]; + int i = 0; + for (Uri mUri : uris) { + paths[i++] = Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 ? getFileAbsolutePath(activity, mUri) : getRealPathBelowVersion(activity, mUri); + + } + return paths; + } catch (Throwable throwable) { + if (LogUtils.isDebug()) { + throwable.printStackTrace(); + } + } + return null; + + } + + private static String getRealPathBelowVersion(Context context, Uri uri) { + String filePath = null; + LogUtils.i(TAG, "method -> getRealPathBelowVersion " + uri + " path:" + uri.getPath() + " getAuthority:" + uri.getAuthority()); + String[] projection = {MediaStore.Images.Media.DATA}; + CursorLoader loader = new CursorLoader(context, uri, projection, null, + null, null); + Cursor cursor = loader.loadInBackground(); + if (cursor != null) { + cursor.moveToFirst(); + filePath = cursor.getString(cursor.getColumnIndex(projection[0])); + cursor.close(); + } + if (filePath == null) { + filePath = uri.getPath(); + } + return filePath; + } + + static File createImageFile(Context context) { + File mFile = null; + try { + String timeStamp = + new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()); + String imageName = String.format("aw_%s.jpg", timeStamp); + mFile = createFileByName(context, imageName, true); + } catch (Throwable e) { + e.printStackTrace(); + } + return mFile; + } + + static File createVideoFile(Context context){ + File mFile = null; + try { + String timeStamp = + new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()); + String imageName = String.format("aw_%s.mp4", timeStamp); //默认生成mp4 + mFile = createFileByName(context, imageName, true); + } catch (Throwable e) { + e.printStackTrace(); + } + return mFile; + } + + + public static void closeIO(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @TargetApi(19) + static String getFileAbsolutePath(Activity context, Uri fileUri) { + if (context == null || fileUri == null) { + return null; + } + LogUtils.i(TAG, "getAuthority:" + fileUri.getAuthority() + " getHost:" + fileUri.getHost() + " getPath:" + fileUri.getPath() + " getScheme:" + fileUri.getScheme() + " query:" + fileUri.getQuery()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, fileUri)) { + if (isExternalStorageDocument(fileUri)) { + String docId = DocumentsContract.getDocumentId(fileUri); + String[] split = docId.split(":"); + String type = split[0]; + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } else if (isDownloadsDocument(fileUri)) { + String id = DocumentsContract.getDocumentId(fileUri); + Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } else if (isMediaDocument(fileUri)) { + String docId = DocumentsContract.getDocumentId(fileUri); + String[] split = docId.split(":"); + String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + String selection = MediaStore.Images.Media._ID + "=?"; + String[] selectionArgs = new String[]{split[1]}; + return getDataColumn(context, contentUri, selection, selectionArgs); + } else { + } + } // MediaStore (and general) + else if (fileUri.getAuthority().equalsIgnoreCase(context.getPackageName() + ".AgentWebFileProvider")) { + String path = fileUri.getPath(); + int index = path.lastIndexOf("/"); + return getAgentWebFilePath(context) + File.separator + path.substring(index + 1, path.length()); + } else if ("content".equalsIgnoreCase(fileUri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(fileUri)) { + return fileUri.getLastPathSegment(); + } + return getDataColumn(context, fileUri, null, null); + } + // File + else if ("file".equalsIgnoreCase(fileUri.getScheme())) { + return fileUri.getPath(); + } + return null; + } + + static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + String[] projection = {MediaStore.Images.Media.DATA}; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + return cursor.getString(index); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + static Intent getInstallApkIntentCompat(Context context, File file) { + Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW); + setIntentDataAndType(context, mIntent, "application/vnd.android.package-archive", file, false); + return mIntent; + } + + public static Intent getCommonFileIntentCompat(Context context, File file) { + Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW); + setIntentDataAndType(context, mIntent, getMIMEType(file), file, false); + return mIntent; + } + + static Intent getIntentCaptureCompat(Context context, File file) { + Intent mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + Uri mUri = getUriFromFile(context, file); + mIntent.addCategory(Intent.CATEGORY_DEFAULT); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); + return mIntent; + } + + static Intent getIntentVideoCompat(Context context, File file){ + Intent mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + Uri mUri = getUriFromFile(context, file); + mIntent.addCategory(Intent.CATEGORY_DEFAULT); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); + return mIntent; + } + + + static boolean isJson(String target) { + if (TextUtils.isEmpty(target)) { + return false; + } + boolean tag = false; + try { + if (target.startsWith("[")) { + new JSONArray(target); + } else { + new JSONObject(target); + } + tag = true; + } catch (JSONException ignore) { // ignore.printStackTrace(); - tag = false; - } - - return tag; - - } - - - public static boolean isUIThread() { - return Looper.myLooper() == Looper.getMainLooper(); - } - - static boolean isEmptyCollection(Collection collection) { - - return collection == null || collection.isEmpty(); - } - - static boolean isEmptyMap(Map map) { - - return map == null || map.isEmpty(); - } - - private static Toast mToast = null; - - static void toastShowShort(Context context, String msg) { - - if (mToast == null) { - mToast = Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT); - } else { - mToast.setText(msg); - } - mToast.show(); - - } - - @Deprecated - static void getUIControllerAndShowMessage(Activity activity, String message, String from) { - - if (activity == null || activity.isFinishing()) { - return; - } - WebParentLayout mWebParentLayout = activity.findViewById(R.id.web_parent_layout_id); - AbsAgentWebUIController mAgentWebUIController = mWebParentLayout.provide(); - if (mAgentWebUIController != null) { - mAgentWebUIController.onShowMessage(message, from); - } - } - - public static boolean hasPermission(@NonNull Context context, @NonNull String... permissions) { - return hasPermission(context, Arrays.asList(permissions)); - } - - public static boolean hasPermission(@NonNull Context context, @NonNull List permissions) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true; - } - for (String permission : permissions) { - int result = ContextCompat.checkSelfPermission(context, permission); - if (result == PackageManager.PERMISSION_DENIED) { - return false; - } - - String op = AppOpsManagerCompat.permissionToOp(permission); - if (TextUtils.isEmpty(op)) { - continue; - } - result = AppOpsManagerCompat.noteProxyOp(context, op, context.getPackageName()); - if (result != AppOpsManagerCompat.MODE_ALLOWED) { - return false; - } - - } - return true; - } - - public static List getDeniedPermissions(Activity activity, String[] permissions) { - - if (permissions == null || permissions.length == 0) { - return null; - } - List deniedPermissions = new ArrayList<>(); - for (int i = 0; i < permissions.length; i++) { - - if (!hasPermission(activity, permissions[i])) { - deniedPermissions.add(permissions[i]); - } - } - return deniedPermissions; - - } - - - public static AbsAgentWebUIController getAgentWebUIControllerByWebView(WebView webView) { - WebParentLayout mWebParentLayout = getWebParentLayoutByWebView(webView); - return mWebParentLayout.provide(); - } - - //获取应用的名称 - public static String getApplicationName(Context context) { - PackageManager packageManager = null; - ApplicationInfo applicationInfo = null; - try { - packageManager = context.getApplicationContext().getPackageManager(); - applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - applicationInfo = null; - } - String applicationName = - (String) packageManager.getApplicationLabel(applicationInfo); - return applicationName; - } - - static WebParentLayout getWebParentLayoutByWebView(WebView webView) { - ViewGroup mViewGroup = null; - if (!(webView.getParent() instanceof ViewGroup)) { - throw new IllegalStateException("please check webcreator's create method was be called ?"); - } - mViewGroup = (ViewGroup) webView.getParent(); - AbsAgentWebUIController mAgentWebUIController; - while (mViewGroup != null) { - - LogUtils.i(TAG, "ViewGroup:" + mViewGroup); - if (mViewGroup.getId() == R.id.web_parent_layout_id) { - WebParentLayout mWebParentLayout = (WebParentLayout) mViewGroup; - LogUtils.i(TAG, "found WebParentLayout"); - return mWebParentLayout; - } else { - ViewParent mViewParent = mViewGroup.getParent(); - if (mViewParent instanceof ViewGroup) { - mViewGroup = (ViewGroup) mViewParent; - } else { - mViewGroup = null; - } - } - } - throw new IllegalStateException("please check webcreator's create method was be called ?"); - } - - public static void runInUiThread(Runnable runnable) { - if (mHandler == null) { - mHandler = new Handler(Looper.getMainLooper()); - } - mHandler.post(runnable); - } - - static boolean showFileChooserCompat(Activity activity, - WebView webView, - ValueCallback valueCallbacks, - WebChromeClient.FileChooserParams fileChooserParams, - PermissionInterceptor permissionInterceptor, - ValueCallback valueCallback, - String mimeType, - Handler.Callback jsChannelCallback - ) { - - - try { - - Class clz = Class.forName("com.just.agentweb.filechooser.FileChooser"); - Object mFileChooser$Builder = clz.getDeclaredMethod("newBuilder", - Activity.class, WebView.class) - .invoke(null, activity, webView); - clz = mFileChooser$Builder.getClass(); - Method mMethod = null; - if (valueCallbacks != null) { - mMethod = clz.getDeclaredMethod("setUriValueCallbacks", ValueCallback.class); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser$Builder, valueCallbacks); - } - - if (fileChooserParams != null) { - mMethod = clz.getDeclaredMethod("setFileChooserParams", WebChromeClient.FileChooserParams.class); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser$Builder, fileChooserParams); - } - - if (valueCallback != null) { - mMethod = clz.getDeclaredMethod("setUriValueCallback", ValueCallback.class); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser$Builder, valueCallback); - } - - - if (!TextUtils.isEmpty(mimeType)) { + tag = false; + } + return tag; + } + + public static boolean isUIThread() { + return Looper.myLooper() == Looper.getMainLooper(); + } + + static boolean isEmptyCollection(Collection collection) { + return collection == null || collection.isEmpty(); + } + + static boolean isEmptyMap(Map map) { + return map == null || map.isEmpty(); + } + + private static Toast mToast = null; + static void toastShowShort(Context context, String msg) { + if (mToast == null) { + mToast = Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT); + } else { + mToast.setText(msg); + } + mToast.show(); + } + + @Deprecated + static void getUIControllerAndShowMessage(Activity activity, String message, String from) { + if (activity == null || activity.isFinishing()) { + return; + } + WebParentLayout mWebParentLayout = (WebParentLayout) activity.findViewById(R.id.web_parent_layout_id); + AbsAgentWebUIController mAgentWebUIController = mWebParentLayout.provide(); + if (mAgentWebUIController != null) { + mAgentWebUIController.onShowMessage(message, from); + } + } + + public static boolean hasPermission(@NonNull Context context, @NonNull String... permissions) { + return hasPermission(context, Arrays.asList(permissions)); + } + + public static boolean hasPermission(@NonNull Context context, @NonNull List permissions) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + for (String permission : permissions) { + int result = ContextCompat.checkSelfPermission(context, permission); + if (result == PackageManager.PERMISSION_DENIED) { + return false; + } + String op = AppOpsManagerCompat.permissionToOp(permission); + if (TextUtils.isEmpty(op)) { + continue; + } + result = AppOpsManagerCompat.noteProxyOp(context, op, context.getPackageName()); + if (result != AppOpsManagerCompat.MODE_ALLOWED) { + return false; + } + } + return true; + } + + public static List getDeniedPermissions(Activity activity, String[] permissions) { + if (permissions == null || permissions.length == 0) { + return null; + } + List deniedPermissions = new ArrayList<>(); + for (int i = 0; i < permissions.length; i++) { + if (!hasPermission(activity, permissions[i])) { + deniedPermissions.add(permissions[i]); + } + } + return deniedPermissions; + } + + + public static AbsAgentWebUIController getAgentWebUIControllerByWebView(WebView webView) { + WebParentLayout mWebParentLayout = getWebParentLayoutByWebView(webView); + return mWebParentLayout.provide(); + } + + //获取应用的名称 + public static String getApplicationName(Context context) { + PackageManager packageManager = null; + ApplicationInfo applicationInfo = null; + try { + packageManager = context.getApplicationContext().getPackageManager(); + applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + applicationInfo = null; + } + String applicationName = + (String) packageManager.getApplicationLabel(applicationInfo); + return applicationName; + } + + static WebParentLayout getWebParentLayoutByWebView(WebView webView) { + ViewGroup mViewGroup = null; + if (!(webView.getParent() instanceof ViewGroup)) { + throw new IllegalStateException("please check webcreator's create method was be called ?"); + } + mViewGroup = (ViewGroup) webView.getParent(); + AbsAgentWebUIController mAgentWebUIController; + while (mViewGroup != null) { + + LogUtils.i(TAG, "ViewGroup:" + mViewGroup); + if (mViewGroup.getId() == R.id.web_parent_layout_id) { + WebParentLayout mWebParentLayout = (WebParentLayout) mViewGroup; + LogUtils.i(TAG, "found WebParentLayout"); + return mWebParentLayout; + } else { + ViewParent mViewParent = mViewGroup.getParent(); + if (mViewParent instanceof ViewGroup) { + mViewGroup = (ViewGroup) mViewParent; + } else { + mViewGroup = null; + } + } + } + throw new IllegalStateException("please check webcreator's create method was be called ?"); + } + + public static void runInUiThread(Runnable runnable) { + if (mHandler == null) { + mHandler = new Handler(Looper.getMainLooper()); + } + mHandler.post(runnable); + } + + static boolean showFileChooserCompat(Activity activity, + WebView webView, + ValueCallback valueCallbacks, + WebChromeClient.FileChooserParams fileChooserParams, + PermissionInterceptor permissionInterceptor, + ValueCallback valueCallback, + String mimeType, + Handler.Callback jsChannelCallback + ) { + try { + Class clz = Class.forName("com.just.agentweb.filechooser.FileChooser"); + Object mFileChooser$Builder = clz.getDeclaredMethod("newBuilder", + Activity.class, WebView.class) + .invoke(null, activity, webView); + clz = mFileChooser$Builder.getClass(); + Method mMethod = null; + if (valueCallbacks != null) { + mMethod = clz.getDeclaredMethod("setUriValueCallbacks", ValueCallback.class); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser$Builder, valueCallbacks); + } + if (fileChooserParams != null) { + mMethod = clz.getDeclaredMethod("setFileChooserParams", WebChromeClient.FileChooserParams.class); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser$Builder, fileChooserParams); + } + if (valueCallback != null) { + mMethod = clz.getDeclaredMethod("setUriValueCallback", ValueCallback.class); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser$Builder, valueCallback); + } + if (!TextUtils.isEmpty(mimeType)) { // LogUtils.i(TAG, Arrays.toString(clz.getDeclaredMethods())); - mMethod = clz.getDeclaredMethod("setAcceptType", String.class); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser$Builder, mimeType); - } - - if (jsChannelCallback != null) { - mMethod = clz.getDeclaredMethod("setJsChannelCallback", Handler.Callback.class); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser$Builder, jsChannelCallback); - } - - - mMethod = clz.getDeclaredMethod("setPermissionInterceptor", PermissionInterceptor.class); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser$Builder, permissionInterceptor); - - mMethod = clz.getDeclaredMethod("build"); - mMethod.setAccessible(true); - Object mFileChooser = mMethod.invoke(mFileChooser$Builder); - - mMethod = mFileChooser.getClass().getDeclaredMethod("openFileChooser"); - mMethod.setAccessible(true); - mMethod.invoke(mFileChooser); - - } catch (Throwable throwable) { - if (LogUtils.isDebug()) { - throwable.printStackTrace(); - } - if (valueCallbacks != null) { - LogUtils.i(TAG, "onReceiveValue empty"); - return false; - } - if (valueCallback != null) { - valueCallback.onReceiveValue(null); - } - } - return true; - } - - public static String md5(String str) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.update(str.getBytes()); - return new BigInteger(1, md.digest()).toString(16); - } catch (Exception e) { - if (LogUtils.isDebug()) { - e.printStackTrace(); - } - } - return ""; - } + mMethod = clz.getDeclaredMethod("setAcceptType", String.class); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser$Builder, mimeType); + } + if (jsChannelCallback != null) { + mMethod = clz.getDeclaredMethod("setJsChannelCallback", Handler.Callback.class); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser$Builder, jsChannelCallback); + } + mMethod = clz.getDeclaredMethod("setPermissionInterceptor", PermissionInterceptor.class); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser$Builder, permissionInterceptor); + mMethod = clz.getDeclaredMethod("build"); + mMethod.setAccessible(true); + Object mFileChooser = mMethod.invoke(mFileChooser$Builder); + mMethod = mFileChooser.getClass().getDeclaredMethod("openFileChooser"); + mMethod.setAccessible(true); + mMethod.invoke(mFileChooser); + } catch (Throwable throwable) { + if (LogUtils.isDebug()) { + throwable.printStackTrace(); + } + if (throwable instanceof ClassNotFoundException) { + LogUtils.e(TAG, "Please check whether compile'com.just.agentweb:filechooser:x.x.x' dependency was added."); + } + if (valueCallbacks != null) { + LogUtils.i(TAG, "onReceiveValue empty"); + return false; + } + if (valueCallback != null) { + valueCallback.onReceiveValue(null); + } + } + return true; + } + + public static String md5(String str) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(str.getBytes()); + return new BigInteger(1, md.digest()).toString(16); + } catch (Exception e) { + if (LogUtils.isDebug()) { + e.printStackTrace(); + } + } + return ""; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/AgentWebView.java b/agentweb-core/src/main/java/com/just/agentweb/AgentWebView.java index 85e7bfa..1940aeb 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/AgentWebView.java +++ b/agentweb-core/src/main/java/com/just/agentweb/AgentWebView.java @@ -48,7 +48,7 @@ * @author cenxiaozhong * @since 1.0.0 */ -public class AgentWebView extends WebView { +public class AgentWebView extends LollipopFixedWebView { private static final String TAG = AgentWebView.class.getSimpleName(); private Map mJsCallJavas; private Map mInjectJavaScripts; @@ -78,14 +78,16 @@ public AgentWebView(Context context, AttributeSet attrs) { @Override @Deprecated public final void addJavascriptInterface(Object interfaceObj, String interfaceName) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { super.addJavascriptInterface(interfaceObj, interfaceName); Log.i(TAG, "注入"); return; + } else { + Log.i(TAG, "use mJsCallJavas:" + interfaceName); } - Log.i(TAG, "use mJsCallJavas:" + interfaceName); - Log.i(TAG, "addJavascriptInterface:" + interfaceObj + " interfaceName:" + interfaceName); + LogUtils.i(TAG, "addJavascriptInterface:" + interfaceObj + " interfaceName:" + interfaceName); if (mJsCallJavas == null) { mJsCallJavas = new HashMap(); } @@ -98,7 +100,6 @@ public final void addJavascriptInterface(Object interfaceObj, String interfaceNa } protected void addJavascriptInterfaceSupport(Object interfaceObj, String interfaceName) { - } @Override @@ -111,7 +112,6 @@ public final void setWebChromeClient(WebChromeClient client) { } protected final void setWebChromeClientSupport(WebChromeClient client) { - } @Override @@ -123,7 +123,6 @@ public final void setWebViewClient(WebViewClient client) { } public final void setWebViewClientSupport(WebViewClient client) { - } @Override @@ -140,7 +139,6 @@ public void destroy() { releaseConfigCallback(); if (mIsInited) { resetAccessibilityEnabled(); -// LogUtils.i(TAG, "destroy web"); super.destroy(); } @@ -337,12 +335,9 @@ public boolean onJsPrompt(WebView view, String url, String message, String defau } else { return super.onJsPrompt(view, url, message, defaultValue, result); } - } - } - /** * 解决部分手机webView返回时不触发onReceivedTitle的问题(如:三星SM-G9008V 4.4.2); */ @@ -360,7 +355,6 @@ public void onPageStarted() { public void onPageFinished(WebView view) { if (!mIsOnReceivedTitle && mWebChromeClient != null) { - WebBackForwardList list = null; try { list = view.copyBackForwardList(); @@ -378,7 +372,6 @@ public void onPageFinished(WebView view) { } } } - public void onReceivedTitle() { mIsOnReceivedTitle = true; } @@ -457,7 +450,6 @@ protected void trySetWebDebuggEnabled() { } } - @TargetApi(11) protected boolean removeSearchBoxJavaBridge() { try { @@ -475,7 +467,6 @@ protected boolean removeSearchBoxJavaBridge() { return false; } - protected void fixedAccessibilityInjectorException() { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1 && mIsAccessibilityEnabledOriginal == null @@ -485,7 +476,6 @@ && isAccessibilityEnabled()) { } } - protected void fixedAccessibilityInjectorExceptionForOnPageFinished(String url) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN && getSettings().getJavaScriptEnabled() @@ -534,6 +524,4 @@ private void resetAccessibilityEnabled() { setAccessibilityEnabled(mIsAccessibilityEnabledOriginal); } } - - } \ No newline at end of file diff --git a/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorSpec.java b/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorSpec.java index fcbeb07..95cd1d9 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorSpec.java +++ b/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorSpec.java @@ -17,14 +17,13 @@ package com.just.agentweb; + /** * @author cenxiaozhong * @since 1.0.0 */ public interface BaseIndicatorSpec { - /** - * indicator - */ + void show(); void hide(); diff --git a/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorView.java b/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorView.java index 05560b2..683ede4 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorView.java +++ b/agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorView.java @@ -17,17 +17,18 @@ package com.just.agentweb; import android.content.Context; -import android.support.annotation.Nullable; import android.util.AttributeSet; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + /** * @author cenxiaozhong * @date 2017/5/12 * @since 1.0.0 */ -public abstract class BaseIndicatorView extends FrameLayout implements BaseIndicatorSpec, LayoutParamsOffer { +public abstract class BaseIndicatorView extends FrameLayout implements BaseIndicatorSpec,LayoutParamsOffer{ public BaseIndicatorView(Context context) { super(context); } @@ -42,22 +43,17 @@ public BaseIndicatorView(Context context, @Nullable AttributeSet attrs, int defS @Override public void reset() { - } @Override public void setProgress(int newProgress) { - } @Override public void show() { - } @Override public void hide() { - } - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/BaseJsAccessEntrace.java b/agentweb-core/src/main/java/com/just/agentweb/BaseJsAccessEntrace.java index 1551753..5e6b68b 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/BaseJsAccessEntrace.java +++ b/agentweb-core/src/main/java/com/just/agentweb/BaseJsAccessEntrace.java @@ -26,43 +26,32 @@ * @since 1.0.0 */ public abstract class BaseJsAccessEntrace implements JsAccessEntrace { - private WebView mWebView; - public static final String TAG = BaseJsAccessEntrace.class.getSimpleName(); - - BaseJsAccessEntrace(WebView webView) { - this.mWebView = webView; + public static final String TAG=BaseJsAccessEntrace.class.getSimpleName(); + BaseJsAccessEntrace(WebView webView){ + this.mWebView=webView; } - @Override public void callJs(String js, final ValueCallback callback) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { this.evaluateJs(js, callback); } else { this.loadJs(js); } - } - @Override public void callJs(String js) { - this.callJs(js, null); + this.callJs(js, null); } - private void loadJs(String js) { - mWebView.loadUrl(js); - } - - private void evaluateJs(String js, final ValueCallback callback) { - + private void evaluateJs(String js, final ValueCallbackcallback){ mWebView.evaluateJavascript(js, new ValueCallback() { @Override public void onReceiveValue(String value) { - if (callback != null) { + if (callback != null){ callback.onReceiveValue(value); } } @@ -72,50 +61,39 @@ public void onReceiveValue(String value) { @Override public void quickCallJs(String method, ValueCallback callback, String... params) { - - StringBuilder sb = new StringBuilder(); - sb.append("javascript:" + method); - if (params == null || params.length == 0) { + StringBuilder sb=new StringBuilder(); + sb.append("javascript:"+method); + if(params==null||params.length==0){ sb.append("()"); - } else { + }else{ sb.append("(").append(concat(params)).append(")"); } - - - callJs(sb.toString(), callback); - + callJs(sb.toString(),callback); } - private String concat(String... params) { - - StringBuilder mStringBuilder = new StringBuilder(); - - for (int i = 0; i < params.length; i++) { - String param = params[i]; - if (!AgentWebUtils.isJson(param)) { - + private String concat(String...params){ + StringBuilder mStringBuilder=new StringBuilder(); + for(int i=0;i mActivityWeakReference = null; - /** - * DefaultChromeClient 's TAG - */ - private String TAG = DefaultChromeClient.class.getSimpleName(); - /** - * Android WebChromeClient path ,用于反射,用户是否重写来该方法 - */ - public static final String ANDROID_WEBCHROMECLIENT_PATH = "android.webkit.WebChromeClient"; - /** - * WebChromeClient - */ - private WebChromeClient mWebChromeClient; - /** - * 是否被包装过 - */ - private boolean mIsWrapper = false; - /** - * Video 处理类 - */ - private IVideo mIVideo; - /** - * PermissionInterceptor 权限拦截器 - */ - private PermissionInterceptor mPermissionInterceptor; - /** - * 当前 WebView - */ - private WebView mWebView; - /** - * Web端触发的定位 mOrigin - */ - private String mOrigin = null; - /** - * Web 端触发的定位 Callback 回调成功,或者失败 - */ - private GeolocationPermissions.Callback mCallback = null; - /** - * 标志位 - */ - public static final int FROM_CODE_INTENTION = 0x18; - /** - * 标识当前是获取定位权限 - */ - public static final int FROM_CODE_INTENTION_LOCATION = FROM_CODE_INTENTION << 2; - /** - * AbsAgentWebUIController - */ - private WeakReference mAgentWebUIController = null; - /** - * IndicatorController 进度条控制器 - */ - private IndicatorController mIndicatorController; - /** - * 文件选择器 - */ - private Object mFileChooser; - - DefaultChromeClient(Activity activity, - IndicatorController indicatorController, - WebChromeClient chromeClient, - @Nullable IVideo iVideo, - PermissionInterceptor permissionInterceptor, WebView webView) { - super(chromeClient); - this.mIndicatorController = indicatorController; - mIsWrapper = chromeClient != null; - this.mWebChromeClient = chromeClient; - mActivityWeakReference = new WeakReference(activity); - this.mIVideo = iVideo; - this.mPermissionInterceptor = permissionInterceptor; - this.mWebView = webView; - mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView)); - } - - - @Override - public void onProgressChanged(WebView view, int newProgress) { - super.onProgressChanged(view, newProgress); - - if (mIndicatorController != null) { - mIndicatorController.progress(view, newProgress); - } - - } - - @Override - public void onReceivedTitle(WebView view, String title) { - if (mIsWrapper) { - super.onReceivedTitle(view, title); - } - } - - @Override - public boolean onJsAlert(WebView view, String url, String message, JsResult result) { - - - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onJsAlert", "public boolean " + ANDROID_WEBCHROMECLIENT_PATH + ".onJsAlert", WebView.class, String.class, String.class, JsResult.class)) { - return super.onJsAlert(view, url, message, result); - } - - if (mAgentWebUIController.get() != null) { - mAgentWebUIController.get().onJsAlert(view, url, message); - } - - result.confirm(); - - return true; - } - - - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - super.onReceivedIcon(view, icon); - } - - @Override - public void onGeolocationPermissionsHidePrompt() { - super.onGeolocationPermissionsHidePrompt(); - } - - //location - @Override - public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { - - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onGeolocationPermissionsShowPrompt", "public void " + ANDROID_WEBCHROMECLIENT_PATH + ".onGeolocationPermissionsShowPrompt", String.class, GeolocationPermissions.Callback.class)) { - super.onGeolocationPermissionsShowPrompt(origin, callback); - return; - } - onGeolocationPermissionsShowPromptInternal(origin, callback); - } - - - private void onGeolocationPermissionsShowPromptInternal(String origin, GeolocationPermissions.Callback callback) { - - if (mPermissionInterceptor != null) { - if (mPermissionInterceptor.intercept(this.mWebView.getUrl(), AgentWebPermissions.LOCATION, "location")) { - callback.invoke(origin, false, false); - return; - } - } - - Activity mActivity = mActivityWeakReference.get(); - if (mActivity == null) { - callback.invoke(origin, false, false); - return; - } - - List deniedPermissions = null; - if ((deniedPermissions = AgentWebUtils.getDeniedPermissions(mActivity, AgentWebPermissions.LOCATION)).isEmpty()) { - LogUtils.i(TAG, "onGeolocationPermissionsShowPromptInternal:" + true); - callback.invoke(origin, true, false); - } else { - - Action mAction = Action.createPermissionsAction(deniedPermissions.toArray(new String[]{})); - mAction.setFromIntention(FROM_CODE_INTENTION_LOCATION); - ActionActivity.setPermissionListener(mPermissionListener); - this.mCallback = callback; - this.mOrigin = origin; - ActionActivity.start(mActivity, mAction); - } - - - } - - private ActionActivity.PermissionListener mPermissionListener = new ActionActivity.PermissionListener() { - @Override - public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) { - - - if (extras.getInt(KEY_FROM_INTENTION) == FROM_CODE_INTENTION_LOCATION) { - boolean hasPermission = AgentWebUtils.hasPermission(mActivityWeakReference.get(), permissions); - - if (mCallback != null) { - if (hasPermission) { - mCallback.invoke(mOrigin, true, false); - } else { - mCallback.invoke(mOrigin, false, false); - } - - mCallback = null; - mOrigin = null; - } - - if (!hasPermission && null != mAgentWebUIController.get()) { - mAgentWebUIController - .get() - .onPermissionsDeny( - AgentWebPermissions.LOCATION, - AgentWebPermissions.ACTION_LOCATION, - "Location"); - } - } - - } - }; - - @Override - public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - - - try { - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onJsPrompt", "public boolean " + ANDROID_WEBCHROMECLIENT_PATH + ".onJsPrompt", WebView.class, String.class, String.class, String.class, JsPromptResult.class)) { - return super.onJsPrompt(view, url, message, defaultValue, result); - } - if (this.mAgentWebUIController.get() != null) { - this.mAgentWebUIController.get().onJsPrompt(mWebView, url, message, defaultValue, result); - } - } catch (Exception e) { - if (LogUtils.isDebug()) { - e.printStackTrace(); - } - } - - return true; - } - - @Override - public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { - - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onJsConfirm", "public boolean " + ANDROID_WEBCHROMECLIENT_PATH + ".onJsConfirm", WebView.class, String.class, String.class, JsResult.class)) { - return super.onJsConfirm(view, url, message, result); - } - - if (mAgentWebUIController.get() != null) { - mAgentWebUIController.get().onJsConfirm(view, url, message, result); - } - return true; - } - - - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) { - - - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onExceededDatabaseQuota", ANDROID_WEBCHROMECLIENT_PATH + ".onExceededDatabaseQuota", String.class, String.class, long.class, long.class, long.class, WebStorage.QuotaUpdater.class)) { - - super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); - return; - } - quotaUpdater.updateQuota(totalQuota * 2); - } - - @Override - public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) { - - - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onReachedMaxAppCacheSize", ANDROID_WEBCHROMECLIENT_PATH + ".onReachedMaxAppCacheSize", long.class, long.class, WebStorage.QuotaUpdater.class)) { - - super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); - return; - } - quotaUpdater.updateQuota(requiredStorage * 2); - } - - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - LogUtils.i(TAG, "openFileChooser>=5.0"); - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onShowFileChooser", ANDROID_WEBCHROMECLIENT_PATH + ".onShowFileChooser", WebView.class, ValueCallback.class, FileChooserParams.class)) { - - return super.onShowFileChooser(webView, filePathCallback, fileChooserParams); - } - - return openFileChooserAboveL(webView, filePathCallback, fileChooserParams); - } - - private boolean openFileChooserAboveL(WebView webView, ValueCallback valueCallbacks, FileChooserParams fileChooserParams) { - - - LogUtils.i(TAG, "fileChooserParams:" + fileChooserParams.getAcceptTypes() + " getTitle:" + fileChooserParams.getTitle() + " accept:" + Arrays.toString(fileChooserParams.getAcceptTypes()) + " length:" + fileChooserParams.getAcceptTypes().length + " :" + fileChooserParams.isCaptureEnabled() + " " + fileChooserParams.getFilenameHint() + " intent:" + fileChooserParams.createIntent().toString() + " mode:" + fileChooserParams.getMode()); - - Activity mActivity = this.mActivityWeakReference.get(); - if (mActivity == null || mActivity.isFinishing()) { - return false; - } - - return AgentWebUtils.showFileChooserCompat(mActivity, - mWebView, - valueCallbacks, - fileChooserParams, - this.mPermissionInterceptor, - null, - null, - null - ); - - } - - /** - * Android >= 4.1 - * - * @param uploadFile ValueCallback , File URI callback - * @param acceptType - * @param capture - */ - @Override - public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) { - /*believe me , i never want to do this */ - LogUtils.i(TAG, "openFileChooser>=4.1"); - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "openFileChooser", ANDROID_WEBCHROMECLIENT_PATH + ".openFileChooser", ValueCallback.class, String.class, String.class)) { - super.openFileChooser(uploadFile, acceptType, capture); - return; - } - createAndOpenCommonFileChooser(uploadFile, acceptType); - } - - // Android < 3.0 - @Override - public void openFileChooser(ValueCallback valueCallback) { - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "openFileChooser", ANDROID_WEBCHROMECLIENT_PATH + ".openFileChooser", ValueCallback.class)) { - super.openFileChooser(valueCallback); - return; - } - Log.i(TAG, "openFileChooser<3.0"); - createAndOpenCommonFileChooser(valueCallback, "*/*"); - } - - // Android >= 3.0 - @Override - public void openFileChooser(ValueCallback valueCallback, String acceptType) { - Log.i(TAG, "openFileChooser>3.0"); - - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "openFileChooser", ANDROID_WEBCHROMECLIENT_PATH + ".openFileChooser", ValueCallback.class, String.class)) { - super.openFileChooser(valueCallback, acceptType); - return; - } - createAndOpenCommonFileChooser(valueCallback, acceptType); - } - - - private void createAndOpenCommonFileChooser(ValueCallback valueCallback, String mimeType) { - Activity mActivity = this.mActivityWeakReference.get(); - if (mActivity == null || mActivity.isFinishing()) { - valueCallback.onReceiveValue(new Object()); - return; - } - - - AgentWebUtils.showFileChooserCompat(mActivity, - mWebView, - null, - null, - this.mPermissionInterceptor, - valueCallback, - mimeType, - null - ); - - } - - - @Override - public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - super.onConsoleMessage(consoleMessage); - return true; - } - - - @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onShowCustomView", ANDROID_WEBCHROMECLIENT_PATH + ".onShowCustomView", View.class, CustomViewCallback.class)) { - super.onShowCustomView(view, callback); - return; - } - if (mIVideo != null) { - mIVideo.onShowCustomView(view, callback); - } - } - - @Override - public void onHideCustomView() { - if (AgentWebUtils.isOverriedMethod(mWebChromeClient, "onHideCustomView", ANDROID_WEBCHROMECLIENT_PATH + ".onHideCustomView")) { - super.onHideCustomView(); - return; - } - - if (mIVideo != null) { - mIVideo.onHideCustomView(); - } - } + /** + * Activity + */ + private WeakReference mActivityWeakReference = null; + /** + * DefaultChromeClient 's TAG + */ + private String TAG = DefaultChromeClient.class.getSimpleName(); + /** + * Android WebChromeClient path ,用于反射,用户是否重写来该方法 + */ + public static final String ANDROID_WEBCHROMECLIENT_PATH = "android.webkit.WebChromeClient"; + /** + * WebChromeClient + */ + private WebChromeClient mWebChromeClient; + /** + * 包装Flag + */ + private boolean mIsWrapper = false; + /** + * Video 处理类 + */ + private IVideo mIVideo; + /** + * PermissionInterceptor 权限拦截器 + */ + private PermissionInterceptor mPermissionInterceptor; + /** + * 当前 WebView + */ + private WebView mWebView; + /** + * Web端触发的定位 mOrigin + */ + private String mOrigin = null; + /** + * Web 端触发的定位 Callback 回调成功,或者失败 + */ + private GeolocationPermissions.Callback mCallback = null; + /** + * 标志位 + */ + public static final int FROM_CODE_INTENTION = 0x18; + /** + * 标识当前是获取定位权限 + */ + public static final int FROM_CODE_INTENTION_LOCATION = FROM_CODE_INTENTION << 2; + /** + * AbsAgentWebUIController + */ + private WeakReference mAgentWebUIController = null; + /** + * IndicatorController 进度条控制器 + */ + private IndicatorController mIndicatorController; + /** + * 文件选择器 + */ + private Object mFileChooser; + + DefaultChromeClient(Activity activity, + IndicatorController indicatorController, + WebChromeClient chromeClient, + @Nullable IVideo iVideo, + PermissionInterceptor permissionInterceptor, WebView webView) { + super(chromeClient); + this.mIndicatorController = indicatorController; + mIsWrapper = chromeClient != null ? true : false; + this.mWebChromeClient = chromeClient; + mActivityWeakReference = new WeakReference(activity); + this.mIVideo = iVideo; + this.mPermissionInterceptor = permissionInterceptor; + this.mWebView = webView; + mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView)); + } + + + @Override + public void onProgressChanged(WebView view, int newProgress) { + super.onProgressChanged(view, newProgress); + if (mIndicatorController != null) { + mIndicatorController.progress(view, newProgress); + } + } + + @Override + public void onReceivedTitle(WebView view, String title) { + if (mIsWrapper) { + super.onReceivedTitle(view, title); + } + } + + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + if (mAgentWebUIController.get() != null) { + mAgentWebUIController.get().onJsAlert(view, url, message); + } + result.confirm(); + return true; + } + + + @Override + public void onReceivedIcon(WebView view, Bitmap icon) { + super.onReceivedIcon(view, icon); + } + + @Override + public void onGeolocationPermissionsHidePrompt() { + super.onGeolocationPermissionsHidePrompt(); + } + + //location + @Override + public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { + onGeolocationPermissionsShowPromptInternal(origin, callback); + } + + private void onGeolocationPermissionsShowPromptInternal(String origin, GeolocationPermissions.Callback callback) { + if (mPermissionInterceptor != null) { + if (mPermissionInterceptor.intercept(this.mWebView.getUrl(), AgentWebPermissions.LOCATION, "location")) { + callback.invoke(origin, false, false); + return; + } + } + Activity mActivity = mActivityWeakReference.get(); + if (mActivity == null) { + callback.invoke(origin, false, false); + return; + } + List deniedPermissions = null; + if ((deniedPermissions = AgentWebUtils.getDeniedPermissions(mActivity, AgentWebPermissions.LOCATION)).isEmpty()) { + LogUtils.i(TAG, "onGeolocationPermissionsShowPromptInternal:" + true); + callback.invoke(origin, true, false); + } else { + Action mAction = Action.createPermissionsAction(deniedPermissions.toArray(new String[]{})); + mAction.setFromIntention(FROM_CODE_INTENTION_LOCATION); + ActionActivity.setPermissionListener(mPermissionListener); + this.mCallback = callback; + this.mOrigin = origin; + ActionActivity.start(mActivity, mAction); + } + } + + private ActionActivity.PermissionListener mPermissionListener = new ActionActivity.PermissionListener() { + @Override + public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) { + if (extras.getInt(KEY_FROM_INTENTION) == FROM_CODE_INTENTION_LOCATION) { + boolean hasPermission = AgentWebUtils.hasPermission(mActivityWeakReference.get(), permissions); + if (mCallback != null) { + if (hasPermission) { + mCallback.invoke(mOrigin, true, false); + } else { + mCallback.invoke(mOrigin, false, false); + } + mCallback = null; + mOrigin = null; + } + if (!hasPermission && null != mAgentWebUIController.get()) { + mAgentWebUIController + .get() + .onPermissionsDeny( + AgentWebPermissions.LOCATION, + AgentWebPermissions.ACTION_LOCATION, + "Location"); + } + } + } + }; + + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { + try { + if (this.mAgentWebUIController.get() != null) { + this.mAgentWebUIController.get().onJsPrompt(mWebView, url, message, defaultValue, result); + } + } catch (Exception e) { + if (LogUtils.isDebug()) { + e.printStackTrace(); + } + } + return true; + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + if (mAgentWebUIController.get() != null) { + mAgentWebUIController.get().onJsConfirm(view, url, message, result); + } + return true; + } + + + @Override + public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) { + quotaUpdater.updateQuota(totalQuota * 2); + } + + @Override + public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) { + quotaUpdater.updateQuota(requiredStorage * 2); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { + LogUtils.i(TAG, "openFileChooser>=5.0"); + return openFileChooserAboveL(webView, filePathCallback, fileChooserParams); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private boolean openFileChooserAboveL(WebView webView, ValueCallback valueCallbacks, FileChooserParams fileChooserParams) { + LogUtils.i(TAG, "fileChooserParams:" + fileChooserParams.getAcceptTypes() + " getTitle:" + fileChooserParams.getTitle() + " accept:" + Arrays.toString(fileChooserParams.getAcceptTypes()) + " length:" + fileChooserParams.getAcceptTypes().length + " :" + fileChooserParams.isCaptureEnabled() + " " + fileChooserParams.getFilenameHint() + " intent:" + fileChooserParams.createIntent().toString() + " mode:" + fileChooserParams.getMode()); + Activity mActivity = this.mActivityWeakReference.get(); + if (mActivity == null || mActivity.isFinishing()) { + return false; + } + return AgentWebUtils.showFileChooserCompat(mActivity, + mWebView, + valueCallbacks, + fileChooserParams, + this.mPermissionInterceptor, + null, + null, + null + ); + } + + /** + * Android >= 4.1 + * + * @param uploadFile ValueCallback , File URI callback + * @param acceptType + * @param capture + */ + @Override + public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) { + /*believe me , i never want to do this */ + LogUtils.i(TAG, "openFileChooser>=4.1"); + createAndOpenCommonFileChooser(uploadFile, acceptType); + } + + // Android < 3.0 + @Override + public void openFileChooser(ValueCallback valueCallback) { + Log.i(TAG, "openFileChooser<3.0"); + createAndOpenCommonFileChooser(valueCallback, "*/*"); + } + + // Android >= 3.0 + @Override + public void openFileChooser(ValueCallback valueCallback, String acceptType) { + Log.i(TAG, "openFileChooser>3.0"); + createAndOpenCommonFileChooser(valueCallback, acceptType); + } + + + private void createAndOpenCommonFileChooser(ValueCallback valueCallback, String mimeType) { + Activity mActivity = this.mActivityWeakReference.get(); + if (mActivity == null || mActivity.isFinishing()) { + valueCallback.onReceiveValue(new Object()); + return; + } + AgentWebUtils.showFileChooserCompat(mActivity, + mWebView, + null, + null, + this.mPermissionInterceptor, + valueCallback, + mimeType, + null + ); + } + + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + super.onConsoleMessage(consoleMessage); + return true; + } + + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + if (mIVideo != null) { + mIVideo.onShowCustomView(view, callback); + } + } + + @Override + public void onHideCustomView() { + if (mIVideo != null) { + mIVideo.onHideCustomView(); + } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/DefaultDesignUIController.java b/agentweb-core/src/main/java/com/just/agentweb/DefaultDesignUIController.java index 159d0d8..ed922fb 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/DefaultDesignUIController.java +++ b/agentweb-core/src/main/java/com/just/agentweb/DefaultDesignUIController.java @@ -19,12 +19,9 @@ import android.app.Activity; import android.content.DialogInterface; import android.graphics.Color; +import android.os.Build; import android.os.Handler; import android.os.Message; -import android.support.design.widget.BottomSheetDialog; -import android.support.design.widget.Snackbar; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.TypedValue; import android.view.LayoutInflater; @@ -35,6 +32,12 @@ import android.webkit.WebView; import android.widget.TextView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.snackbar.Snackbar; + /** * @author cenxiaozhong * @date 2017/12/8 @@ -42,7 +45,6 @@ */ public class DefaultDesignUIController extends DefaultUIController { - private BottomSheetDialog mBottomSheetDialog; private static final int RECYCLERVIEW_ID = 0x1001; private Activity mActivity = null; @@ -51,9 +53,7 @@ public class DefaultDesignUIController extends DefaultUIController { @Override public void onJsAlert(WebView view, String url, String message) { - onJsAlertInternal(view, message); - } private void onJsAlertInternal(WebView view, String message) { @@ -61,6 +61,11 @@ private void onJsAlertInternal(WebView view, String message) { if (mActivity == null || mActivity.isFinishing()) { return; } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } try { AgentWebUtils.show(view, message, @@ -71,19 +76,17 @@ private void onJsAlertInternal(WebView view, String message) { -1, null); } catch (Throwable throwable) { - if (LogUtils.isDebug()) { + if (LogUtils.isDebug()){ throwable.printStackTrace(); } } } - @Override public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { super.onJsConfirm(view, url, message, jsResult); } - @Override public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) { showChooserInternal(view, url, ways, callback); @@ -95,8 +98,15 @@ public void onForceDownloadAlert(String url, final Handler.Callback callback) { } private void showChooserInternal(WebView view, String url, final String[] ways, final Handler.Callback callback) { - - + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } LogUtils.i(TAG, "url:" + url + " ways:" + ways[0]); RecyclerView mRecyclerView; if (mBottomSheetDialog == null) { @@ -106,7 +116,7 @@ private void showChooserInternal(WebView view, String url, final String[] ways, mRecyclerView.setId(RECYCLERVIEW_ID); mBottomSheetDialog.setContentView(mRecyclerView); } - mRecyclerView = mBottomSheetDialog.getDelegate().findViewById(RECYCLERVIEW_ID); + mRecyclerView = (RecyclerView) mBottomSheetDialog.getDelegate().findViewById(RECYCLERVIEW_ID); mRecyclerView.setAdapter(getAdapter(ways, callback)); mBottomSheetDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override @@ -117,8 +127,6 @@ public void onCancel(DialogInterface dialog) { } }); mBottomSheetDialog.show(); - - } private RecyclerView.Adapter getAdapter(final String[] ways, final Handler.Callback callback) { @@ -134,7 +142,6 @@ public void onBindViewHolder(BottomSheetHolder bottomSheetHolder, final int i) { mActivity.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); bottomSheetHolder.mTextView.setBackgroundResource(outValue.resourceId); bottomSheetHolder.mTextView.setText(ways[i]); - bottomSheetHolder.mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -158,10 +165,9 @@ public int getItemCount() { private static class BottomSheetHolder extends RecyclerView.ViewHolder { TextView mTextView; - public BottomSheetHolder(View itemView) { super(itemView); - mTextView = itemView.findViewById(android.R.id.text1); + mTextView = (TextView) itemView.findViewById(android.R.id.text1); } } @@ -170,7 +176,6 @@ public void onJsPrompt(WebView view, String url, String message, String defaultV super.onJsPrompt(view, url, message, defaultValue, jsPromptResult); } - @Override protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { super.bindSupportWebParent(webParentLayout, activity); @@ -181,6 +186,15 @@ protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity ac @Override public void onShowMessage(String message, String from) { + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } if (!TextUtils.isEmpty(from) && from.contains("performDownload")) { return; } diff --git a/agentweb-core/src/main/java/com/just/agentweb/DefaultDownloadImpl.java b/agentweb-core/src/main/java/com/just/agentweb/DefaultDownloadImpl.java new file mode 100644 index 0000000..d17ea7e --- /dev/null +++ b/agentweb-core/src/main/java/com/just/agentweb/DefaultDownloadImpl.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) Justson(https://github.com/Justson/AgentWeb) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.just.agentweb; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.webkit.WebView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.download.library.DownloadImpl; +import com.download.library.DownloadListenerAdapter; +import com.download.library.Extra; +import com.download.library.ResourceRequest; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author cenxiaozhong + * @date 2017/5/13 + */ +public class DefaultDownloadImpl implements android.webkit.DownloadListener { + /** + * Application Context + */ + protected Context mContext; + protected ConcurrentHashMap mDownloadTasks = new ConcurrentHashMap<>(); + /** + * Activity + */ + protected WeakReference mActivityWeakReference = null; + /** + * TAG 用于打印,标识 + */ + private static final String TAG = DefaultDownloadImpl.class.getSimpleName(); + /** + * 权限拦截 + */ + protected PermissionInterceptor mPermissionListener = null; + /** + * AbsAgentWebUIController + */ + protected WeakReference mAgentWebUIController; + + private static Handler mHandler = new Handler(Looper.getMainLooper()); + + + protected DefaultDownloadImpl(Activity activity, WebView webView, PermissionInterceptor permissionInterceptor) { + this.mContext = activity.getApplicationContext(); + this.mActivityWeakReference = new WeakReference(activity); + this.mPermissionListener = permissionInterceptor; + this.mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView)); + } + + + @Override + public void onDownloadStart(final String url, final String userAgent, final String contentDisposition, final String mimetype, final long contentLength) { + mHandler.post(new Runnable() { + @Override + public void run() { + onDownloadStartInternal(url, userAgent, contentDisposition, mimetype, contentLength); + } + }); + } + + protected void onDownloadStartInternal(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { + if (null == mActivityWeakReference.get() || mActivityWeakReference.get().isFinishing()) { + return; + } + if (null != this.mPermissionListener) { + if (this.mPermissionListener.intercept(url, AgentWebPermissions.STORAGE, "download")) { + return; + } + } + ResourceRequest resourceRequest = createResourceRequest(url); + this.mDownloadTasks.put(url, resourceRequest); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + List mList = null; + if ((mList = checkNeedPermission()).isEmpty()) { + preDownload(url); + } else { + Action mAction = Action.createPermissionsAction(mList.toArray(new String[]{})); + ActionActivity.setPermissionListener(getPermissionListener(url)); + ActionActivity.start(mActivityWeakReference.get(), mAction); + } + } else { + preDownload(url); + } + } + + protected ResourceRequest createResourceRequest(String url) { + return DownloadImpl.getInstance().with(url).setEnableIndicator(true).autoOpenIgnoreMD5(); + } + + protected ActionActivity.PermissionListener getPermissionListener(final String url) { + return new ActionActivity.PermissionListener() { + @Override + public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) { + if (checkNeedPermission().isEmpty()) { + preDownload(url); + } else { + if (null != mAgentWebUIController.get()) { + mAgentWebUIController + .get() + .onPermissionsDeny( + checkNeedPermission(). + toArray(new String[]{}), + AgentWebPermissions.ACTION_STORAGE, "Download"); + } + LogUtils.e(TAG, "储存权限获取失败~"); + } + + } + }; + } + + protected List checkNeedPermission() { + List deniedPermissions = new ArrayList<>(); + if (!AgentWebUtils.hasPermission(mActivityWeakReference.get(), AgentWebPermissions.STORAGE)) { + deniedPermissions.addAll(Arrays.asList(AgentWebPermissions.STORAGE)); + } + return deniedPermissions; + } + + protected void preDownload(String url) { + // 移动数据 + if (!isForceRequest(url) && + AgentWebUtils.checkNetworkType(mContext) > 1) { + showDialog(url); + return; + } + performDownload(url); + } + + protected boolean isForceRequest(String url) { + ResourceRequest resourceRequest = mDownloadTasks.get(url); + if (null != resourceRequest) { + return resourceRequest.getDownloadTask().isForceDownload(); + } + return false; + } + + protected void forceDownload(final String url) { + ResourceRequest resourceRequest = mDownloadTasks.get(url); + resourceRequest.setForceDownload(true); + performDownload(url); + } + + protected void showDialog(final String url) { + Activity mActivity; + if (null == (mActivity = mActivityWeakReference.get()) || mActivity.isFinishing()) { + return; + } + AbsAgentWebUIController mAgentWebUIController; + if (null != (mAgentWebUIController = this.mAgentWebUIController.get())) { + mAgentWebUIController.onForceDownloadAlert(url, createCallback(url)); + } + } + + protected Handler.Callback createCallback(final String url) { + return new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + forceDownload(url); + return true; + } + }; + } + + protected void performDownload(String url) { + try { + LogUtils.e(TAG, "performDownload:" + url + " exist:" + DownloadImpl.getInstance().exist(url)); + // 该链接是否正在下载 + if (DownloadImpl.getInstance().exist(url)) { + if (null != mAgentWebUIController.get()) { + mAgentWebUIController.get().onShowMessage( + mActivityWeakReference.get() + .getString(R.string.agentweb_download_task_has_been_exist), "preDownload"); + } + return; + } + ResourceRequest resourceRequest = mDownloadTasks.get(url); + resourceRequest.addHeader("Cookie", AgentWebConfig.getCookiesByUrl(url)); + taskEnqueue(resourceRequest); + } catch (Throwable ignore) { + if (LogUtils.isDebug()) { + ignore.printStackTrace(); + } + } + } + + protected void taskEnqueue(ResourceRequest resourceRequest) { + resourceRequest.enqueue(new DownloadListenerAdapter() { + @Override + public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { + mDownloadTasks.remove(url); + return super.onResult(throwable, path, url, extra); + } + }); + } + + public static DefaultDownloadImpl create(@NonNull Activity activity, + @NonNull WebView webView, + @Nullable PermissionInterceptor permissionInterceptor) { + try { + DownloadImpl.getInstance().with(activity.getApplication()); + } catch (Throwable throwable) { + LogUtils.e(TAG, "implementation 'com.download.library:Downloader:x.x.x'"); + if (LogUtils.isDebug()) { + throwable.printStackTrace(); + } + } + + return new DefaultDownloadImpl(activity, webView, permissionInterceptor); + } +} diff --git a/agentweb-core/src/main/java/com/just/agentweb/DefaultUIController.java b/agentweb-core/src/main/java/com/just/agentweb/DefaultUIController.java index cda8ffb..f228690 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/DefaultUIController.java +++ b/agentweb-core/src/main/java/com/just/agentweb/DefaultUIController.java @@ -20,15 +20,17 @@ import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.res.Resources; +import android.os.Build; import android.os.Handler; import android.os.Message; -import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.WebView; import android.widget.EditText; +import androidx.appcompat.app.AlertDialog; + /** * @author cenxiaozhong @@ -37,290 +39,331 @@ */ public class DefaultUIController extends AbsAgentWebUIController { - private AlertDialog mAlertDialog; - protected AlertDialog mConfirmDialog; - private JsPromptResult mJsPromptResult = null; - private JsResult mJsResult = null; - private AlertDialog mPromptDialog = null; - private Activity mActivity; - private WebParentLayout mWebParentLayout; - private AlertDialog mAskOpenOtherAppDialog = null; - private ProgressDialog mProgressDialog; - private Resources mResources = null; - - @Override - public void onJsAlert(WebView view, String url, String message) { - AgentWebUtils.toastShowShort(view.getContext().getApplicationContext(), message); - } - - - @Override - public void onOpenPagePrompt(WebView view, String url, final Handler.Callback callback) { - - - LogUtils.i(TAG, "onOpenPagePrompt"); - if (mAskOpenOtherAppDialog == null) { - mAskOpenOtherAppDialog = new AlertDialog - .Builder(mActivity)// - .setMessage(mResources.getString(R.string.agentweb_leave_app_and_go_other_page, - AgentWebUtils.getApplicationName(mActivity)))// - .setTitle(mResources.getString(R.string.agentweb_tips)) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (callback != null) { - callback.handleMessage(Message.obtain(null, -1)); - } - } - })// - .setPositiveButton(mResources.getString(R.string.agentweb_leave), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (callback != null) { - callback.handleMessage(Message.obtain(null, 1)); - } - } - }) - .create(); - } - mAskOpenOtherAppDialog.show(); - } - - @Override - public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { - onJsConfirmInternal(message, jsResult); - } - - @Override - public void onSelectItemsPrompt(WebView view, String url, final String[] ways, final Handler.Callback callback) { - showChooserInternal(ways, callback); - } - - @Override - public void onForceDownloadAlert(String url, final Handler.Callback callback) { - - onForceDownloadAlertInternal(callback); - - } - - private void onForceDownloadAlertInternal(final Handler.Callback callback) { - Activity mActivity; - if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { - return; - } - - - AlertDialog mAlertDialog = null; - mAlertDialog = new AlertDialog.Builder(mActivity) - .setTitle(mResources.getString(R.string.agentweb_tips)) - .setMessage(mResources.getString(R.string.agentweb_honeycomblow)) - .setNegativeButton(mResources.getString(R.string.agentweb_download), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (dialog != null) { - dialog.dismiss(); - } - if (callback != null) { - callback.handleMessage(Message.obtain()); - } - } - })// - .setPositiveButton(mResources.getString(R.string.agentweb_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - - if (dialog != null) { - dialog.dismiss(); - } - } - }).create(); - - mAlertDialog.show(); - } - - private void showChooserInternal(String[] ways, final Handler.Callback callback) { - mAlertDialog = new AlertDialog.Builder(mActivity)// - .setSingleChoiceItems(ways, -1, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - LogUtils.i(TAG, "which:" + which); - if (callback != null) { - Message mMessage = Message.obtain(); - mMessage.what = which; - callback.handleMessage(mMessage); - } - - } - }).setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.dismiss(); - if (callback != null) { - callback.handleMessage(Message.obtain(null, -1)); - } - } - }).create(); - mAlertDialog.show(); - } - - private void onJsConfirmInternal(String message, JsResult jsResult) { - LogUtils.i(TAG, "activity:" + mActivity.hashCode() + " "); - Activity mActivity = this.mActivity; - if (mActivity == null || mActivity.isFinishing()) { - toCancelJsresult(jsResult); - return; - } - - if (mConfirmDialog == null) { - mConfirmDialog = new AlertDialog.Builder(mActivity)// - .setMessage(message)// - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - toDismissDialog(mConfirmDialog); - toCancelJsresult(mJsResult); - } - })// - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - toDismissDialog(mConfirmDialog); - if (mJsResult != null) { - mJsResult.confirm(); - } - - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.dismiss(); - toCancelJsresult(mJsResult); - } - }) - .create(); - - } - mConfirmDialog.setMessage(message); - this.mJsResult = jsResult; - mConfirmDialog.show(); - } - - - private void onJsPromptInternal(String message, String defaultValue, JsPromptResult jsPromptResult) { - Activity mActivity = this.mActivity; - if (mActivity == null || mActivity.isFinishing()) { - jsPromptResult.cancel(); - return; - } - if (mPromptDialog == null) { - - final EditText et = new EditText(mActivity); - et.setText(defaultValue); - mPromptDialog = new AlertDialog.Builder(mActivity)// - .setView(et)// - .setTitle(message) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - toDismissDialog(mPromptDialog); - toCancelJsresult(mJsPromptResult); - } - })// - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - toDismissDialog(mPromptDialog); - - if (mJsPromptResult != null) { - mJsPromptResult.confirm(et.getText().toString()); - } - - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.dismiss(); - toCancelJsresult(mJsPromptResult); - } - }) - .create(); - } - this.mJsPromptResult = jsPromptResult; - mPromptDialog.show(); - } - - @Override - public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { - onJsPromptInternal(message, defaultValue, jsPromptResult); - } - - @Override - public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { - - LogUtils.i(TAG, "mWebParentLayout onMainFrameError:" + mWebParentLayout); - if (mWebParentLayout != null) { - mWebParentLayout.showPageMainFrameError(); - } - } - - @Override - public void onShowMainFrame() { - if (mWebParentLayout != null) { - mWebParentLayout.hideErrorLayout(); - } - } - - @Override - public void onLoading(String msg) { - - if (mProgressDialog == null) { - mProgressDialog = new ProgressDialog(mActivity); - } - mProgressDialog.setCancelable(false); - mProgressDialog.setCanceledOnTouchOutside(false); - mProgressDialog.setMessage(msg); - mProgressDialog.show(); - - } - - @Override - public void onCancelLoading() { - - if (mProgressDialog != null && mProgressDialog.isShowing()) { - mProgressDialog.dismiss(); - } - - mProgressDialog = null; - } - - @Override - public void onShowMessage(String message, String from) { - if (!TextUtils.isEmpty(from) && from.contains("performDownload")) { - return; - } - AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), message); - } - - @Override - public void onPermissionsDeny(String[] permissions, String permissionType, String action) { + private AlertDialog mAlertDialog; + protected AlertDialog mConfirmDialog; + private JsPromptResult mJsPromptResult = null; + private JsResult mJsResult = null; + private AlertDialog mPromptDialog = null; + private Activity mActivity; + private WebParentLayout mWebParentLayout; + private AlertDialog mAskOpenOtherAppDialog = null; + private ProgressDialog mProgressDialog; + private Resources mResources = null; + + @Override + public void onJsAlert(WebView view, String url, String message) { + AgentWebUtils.toastShowShort(view.getContext().getApplicationContext(), message); + } + + @Override + public void onOpenPagePrompt(WebView view, String url, final Handler.Callback callback) { + LogUtils.i(TAG, "onOpenPagePrompt"); + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } + if (mAskOpenOtherAppDialog == null) { + mAskOpenOtherAppDialog = new AlertDialog + .Builder(mActivity) + .setMessage(mResources.getString(R.string.agentweb_leave_app_and_go_other_page, + AgentWebUtils.getApplicationName(mActivity))) + .setTitle(mResources.getString(R.string.agentweb_tips)) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (callback != null) { + callback.handleMessage(Message.obtain(null, -1)); + } + } + })// + .setPositiveButton(mResources.getString(R.string.agentweb_leave), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (callback != null) { + callback.handleMessage(Message.obtain(null, 1)); + } + } + }) + .create(); + } + mAskOpenOtherAppDialog.show(); + } + + @Override + public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { + onJsConfirmInternal(message, jsResult); + } + + @Override + public void onSelectItemsPrompt(WebView view, String url, final String[] ways, final Handler.Callback callback) { + showChooserInternal(ways, callback); + } + + @Override + public void onForceDownloadAlert(String url, final Handler.Callback callback) { + onForceDownloadAlertInternal(callback); + } + + private void onForceDownloadAlertInternal(final Handler.Callback callback) { + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } + AlertDialog mAlertDialog = null; + mAlertDialog = new AlertDialog.Builder(mActivity) + .setTitle(mResources.getString(R.string.agentweb_tips)) + .setMessage(mResources.getString(R.string.agentweb_honeycomblow)) + .setNegativeButton(mResources.getString(R.string.agentweb_download), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (dialog != null) { + dialog.dismiss(); + } + if (callback != null) { + callback.handleMessage(Message.obtain()); + } + } + })// + .setPositiveButton(mResources.getString(R.string.agentweb_cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + if (dialog != null) { + dialog.dismiss(); + } + } + }).create(); + mAlertDialog.show(); + } + + private void showChooserInternal(String[] ways, final Handler.Callback callback) { + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } + mAlertDialog = new AlertDialog.Builder(mActivity) + .setSingleChoiceItems(ways, -1, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + LogUtils.i(TAG, "which:" + which); + if (callback != null) { + Message mMessage = Message.obtain(); + mMessage.what = which; + callback.handleMessage(mMessage); + } + + } + }).setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + if (callback != null) { + callback.handleMessage(Message.obtain(null, -1)); + } + } + }).create(); + mAlertDialog.show(); + } + + private void onJsConfirmInternal(String message, JsResult jsResult) { + LogUtils.i(TAG, "activity:" + mActivity.hashCode() + " "); + Activity mActivity = this.mActivity; + if (mActivity == null || mActivity.isFinishing()) { + toCancelJsresult(jsResult); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + toCancelJsresult(jsResult); + return; + } + } + + if (mConfirmDialog == null) { + mConfirmDialog = new AlertDialog.Builder(mActivity) + .setMessage(message) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + toDismissDialog(mConfirmDialog); + toCancelJsresult(mJsResult); + } + })// + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + toDismissDialog(mConfirmDialog); + if (mJsResult != null) { + mJsResult.confirm(); + } + + } + }) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + toCancelJsresult(mJsResult); + } + }) + .create(); + + } + mConfirmDialog.setMessage(message); + this.mJsResult = jsResult; + mConfirmDialog.show(); + } + + + private void onJsPromptInternal(String message, String defaultValue, JsPromptResult jsPromptResult) { + Activity mActivity = this.mActivity; + if (mActivity == null || mActivity.isFinishing()) { + jsPromptResult.cancel(); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + jsPromptResult.cancel(); + return; + } + } + if (mPromptDialog == null) { + final EditText et = new EditText(mActivity); + et.setText(defaultValue); + mPromptDialog = new AlertDialog.Builder(mActivity) + .setView(et) + .setTitle(message) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + toDismissDialog(mPromptDialog); + toCancelJsresult(mJsPromptResult); + } + })// + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + toDismissDialog(mPromptDialog); + + if (mJsPromptResult != null) { + mJsPromptResult.confirm(et.getText().toString()); + } + + } + }) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + toCancelJsresult(mJsPromptResult); + } + }) + .create(); + } + this.mJsPromptResult = jsPromptResult; + mPromptDialog.show(); + } + + @Override + public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { + onJsPromptInternal(message, defaultValue, jsPromptResult); + } + + @Override + public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { + + LogUtils.i(TAG, "mWebParentLayout onMainFrameError:" + mWebParentLayout); + if (mWebParentLayout != null) { + mWebParentLayout.showPageMainFrameError(); + } + } + + @Override + public void onShowMainFrame() { + if (mWebParentLayout != null) { + mWebParentLayout.hideErrorLayout(); + } + } + + @Override + public void onLoading(String msg) { + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } + if (mProgressDialog == null) { + mProgressDialog = new ProgressDialog(mActivity); + } + mProgressDialog.setCancelable(false); + mProgressDialog.setCanceledOnTouchOutside(false); + mProgressDialog.setMessage(msg); + mProgressDialog.show(); + + } + + @Override + public void onCancelLoading() { + Activity mActivity; + if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (mActivity.isDestroyed()) { + return; + } + } + if (mProgressDialog != null && mProgressDialog.isShowing()) { + mProgressDialog.dismiss(); + } + mProgressDialog = null; + } + + @Override + public void onShowMessage(String message, String from) { + if (!TextUtils.isEmpty(from) && from.contains("performDownload")) { + return; + } + AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), message); + } + + @Override + public void onPermissionsDeny(String[] permissions, String permissionType, String action) { // AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), "权限被冻结"); - } + } - private void toCancelJsresult(JsResult result) { - if (result != null) { - result.cancel(); - } - } + private void toCancelJsresult(JsResult result) { + if (result != null) { + result.cancel(); + } + } - @Override - protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { - this.mActivity = activity; - this.mWebParentLayout = webParentLayout; - mResources = this.mActivity.getResources(); + @Override + protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { + this.mActivity = activity; + this.mWebParentLayout = webParentLayout; + mResources = this.mActivity.getResources(); - } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/DefaultWebClient.java b/agentweb-core/src/main/java/com/just/agentweb/DefaultWebClient.java index 5ad0ea5..836ee3a 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/DefaultWebClient.java +++ b/agentweb-core/src/main/java/com/just/agentweb/DefaultWebClient.java @@ -16,13 +16,16 @@ package com.just.agentweb; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.text.TextUtils; @@ -35,648 +38,626 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.annotation.RequiresApi; + +import com.alipay.sdk.app.H5PayCallback; +import com.alipay.sdk.app.PayTask; +import com.alipay.sdk.util.H5PayResultModel; + import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.util.HashSet; import java.util.List; import java.util.Set; -//import com.alipay.sdk.app.H5PayCallback; -//import com.alipay.sdk.app.PayTask; -//import com.alipay.sdk.util.H5PayResultModel; - /** * @author cenxiaozhong * @since 3.0.0 */ public class DefaultWebClient extends MiddlewareWebClientBase { - - /** - * Activity's WeakReference - */ - private WeakReference mWeakReference = null; - /** - * 缩放 - */ - private static final int CONSTANTS_ABNORMAL_BIG = 7; - /** - * WebViewClient - */ - private WebViewClient mWebViewClient; - /** - * mWebClientHelper - */ - private boolean webClientHelper = true; - /** - * Android WebViewClient ' path 用于反射,判断用户是否重写了WebViewClient的某一个方法 - */ - private static final String ANDROID_WEBVIEWCLIENT_PATH = "android.webkit.WebViewClient"; - /** - * intent ' s scheme - */ - public static final String INTENT_SCHEME = "intent://"; - /** - * Wechat pay scheme ,用于唤醒微信支付 - */ - public static final String WEBCHAT_PAY_SCHEME = "weixin://wap/pay?"; - /** - * 支付宝 - */ - public static final String ALIPAYS_SCHEME = "alipays://"; - /** - * http scheme - */ - public static final String HTTP_SCHEME = "http://"; - /** - * https scheme - */ - public static final String HTTPS_SCHEME = "https://"; - /** - * true 表示当前应用内依赖了 alipay library , false 反之 - */ - private static final boolean HAS_ALIPAY_LIB; - /** - * WebViewClient's tag 用于打印 - */ - private static final String TAG = DefaultWebClient.class.getSimpleName(); - /** - * 直接打开其他页面 - */ - public static final int DERECT_OPEN_OTHER_PAGE = 1001; - /** - * 弹窗咨询用户是否前往其他页面 - */ - public static final int ASK_USER_OPEN_OTHER_PAGE = DERECT_OPEN_OTHER_PAGE >> 2; - /** - * 不允许打开其他页面 - */ - public static final int DISALLOW_OPEN_OTHER_APP = DERECT_OPEN_OTHER_PAGE >> 4; - /** - * 默认为咨询用户 - */ - private int mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE; - /** - * 是否拦截找不到相应页面的Url,默认拦截 - */ - private boolean mIsInterceptUnkownUrl = true; - /** - * AbsAgentWebUIController - */ - private WeakReference mAgentWebUIController = null; - /** - * WebView - */ - private WebView mWebView; - /** - * 弹窗回调 - */ - private Handler.Callback mCallback = null; - /** - * MainFrameErrorMethod - */ - private Method onMainFrameErrorMethod = null; - /** - * Alipay PayTask 对象 - */ - private Object mPayTask; - /** - * SMS scheme - */ - public static final String SCHEME_SMS = "sms:"; - /** - * 缓存当前出现错误的页面 - */ - private Set mErrorUrlsSet = new HashSet<>(); - /** - * 缓存等待加载完成的页面 onPageStart()执行之后 ,onPageFinished()执行之前 - */ - private Set mWaittingFinishSet = new HashSet<>(); - - static { - boolean tag = true; - try { - Class.forName("com.alipay.sdk.app.PayTask"); - } catch (Throwable ignore) { - tag = false; - } - HAS_ALIPAY_LIB = tag; - - LogUtils.i(TAG, "HAS_ALIPAY_LIB:" + HAS_ALIPAY_LIB); - } - - - DefaultWebClient(Builder builder) { - super(builder.mClient); - this.mWebView = builder.mWebView; - this.mWebViewClient = builder.mClient; - mWeakReference = new WeakReference(builder.mActivity); - this.webClientHelper = builder.mWebClientHelper; - mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(builder.mWebView)); - mIsInterceptUnkownUrl = builder.mIsInterceptUnkownScheme; - - if (builder.mUrlHandleWays <= 0) { - mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE; - } else { - mUrlHandleWays = builder.mUrlHandleWays; - } - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - int tag = -1; - - if (AgentWebUtils.isOverriedMethod(mWebViewClient, "shouldOverrideUrlLoading", ANDROID_WEBVIEWCLIENT_PATH + ".shouldOverrideUrlLoading", WebView.class, WebResourceRequest.class) && (((tag = 1) > 0) && super.shouldOverrideUrlLoading(view, request))) { - return true; - } - - String url = request.getUrl().toString(); - - if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) { - return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url)); - } - - if (!webClientHelper) { - return false; - } - if (handleCommonLink(url)) { - return true; - } - - // intent - if (url.startsWith(INTENT_SCHEME)) { - handleIntentUrl(url); - LogUtils.i(TAG, "intent url "); - return true; - } - // 微信支付 - if (url.startsWith(WEBCHAT_PAY_SCHEME)) { - LogUtils.i(TAG, "lookup wechat to pay ~~"); - startActivity(url); - return true; - } - - if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) { - LogUtils.i(TAG, "alipays url lookup alipay ~~ "); - return true; - } - - if (queryActiviesNumber(url) > 0 && urlOpenWays(url)) { - LogUtils.i(TAG, "intercept url:" + url); - return true; - } - if (mIsInterceptUnkownUrl) { - LogUtils.i(TAG, "intercept mIsInterceptUnkownUrl :" + request.getUrl()); - return true; - } - - if (tag > 0) { - return false; - } - return super.shouldOverrideUrlLoading(view, request); - } - - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - return super.shouldInterceptRequest(view, url); - } - - @Override - public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { - super.onReceivedHttpAuthRequest(view, handler, host, realm); - } - - private boolean urlOpenWays(String url) { - - switch (mUrlHandleWays) { - // 直接打开其他App - case DERECT_OPEN_OTHER_PAGE: - lookup(url); - return true; - // 咨询用户是否打开其他App - case ASK_USER_OPEN_OTHER_PAGE: - if (mAgentWebUIController.get() != null) { - mAgentWebUIController.get() - .onOpenPagePrompt(this.mWebView, - mWebView.getUrl(), - getCallback(url)); - } - return true; - // 默认不打开 - default: - return false; - } - } - - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - return super.shouldInterceptRequest(view, request); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - - - int tag = -1; - - if (AgentWebUtils.isOverriedMethod(mWebViewClient, "shouldOverrideUrlLoading", ANDROID_WEBVIEWCLIENT_PATH + ".shouldOverrideUrlLoading", WebView.class, String.class) && (((tag = 1) > 0) && super.shouldOverrideUrlLoading(view, url))) { - return true; - } - - if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) { - return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url)); - } - - if (!webClientHelper) { - return false; - } - //电话 , 邮箱 , 短信 - if (handleCommonLink(url)) { - return true; - } - //Intent scheme - if (url.startsWith(INTENT_SCHEME)) { - handleIntentUrl(url); - return true; - } - //微信支付 - if (url.startsWith(WEBCHAT_PAY_SCHEME)) { - startActivity(url); - return true; - } - //支付宝 - if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) { - return true; - } - //打开url 相对应的页面 - if (queryActiviesNumber(url) > 0 && urlOpenWays(url)) { - LogUtils.i(TAG, "intercept OtherAppScheme"); - return true; - } - // 手机里面没有页面能匹配到该链接 ,拦截下来。 - if (mIsInterceptUnkownUrl) { - LogUtils.i(TAG, "intercept InterceptUnkownScheme : " + url); - return true; - } - - if (tag > 0) { - return false; - } - - return super.shouldOverrideUrlLoading(view, url); - } - - - private int queryActiviesNumber(String url) { - - try { - if (mWeakReference.get() == null) { - return 0; - } - Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); - PackageManager mPackageManager = mWeakReference.get().getPackageManager(); - List mResolveInfos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - return mResolveInfos == null ? 0 : mResolveInfos.size(); - } catch (URISyntaxException ignore) { - if (LogUtils.isDebug()) { - ignore.printStackTrace(); - } - return 0; - } - } - - private void handleIntentUrl(String intentUrl) { - try { - - Intent intent = null; - if (TextUtils.isEmpty(intentUrl) || !intentUrl.startsWith(INTENT_SCHEME)) { - return; - } - - if (lookup(intentUrl)) { - return; - } - } catch (Throwable e) { - if (LogUtils.isDebug()) { - e.printStackTrace(); - } - } - - - } - - private boolean lookup(String url) { - try { - Intent intent; - Activity mActivity = null; - if ((mActivity = mWeakReference.get()) == null) { - return true; - } - PackageManager packageManager = mActivity.getPackageManager(); - intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); - ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); - // 跳到该应用 - if (info != null) { - mActivity.startActivity(intent); - return true; - } - } catch (Throwable ignore) { - if (LogUtils.isDebug()) { - ignore.printStackTrace(); - } - } - - return false; - } - - private boolean isAlipay(final WebView view, String url) { -// -// try { -// -// Activity mActivity = null; -// if ((mActivity = mWeakReference.get()) == null) { -// return false; -// } -// /** -// * 推荐采用的新的二合一接口(payInterceptorWithUrl),只需调用一次 -// */ -// if (mPayTask == null) { -// Class clazz = Class.forName("com.alipay.sdk.app.PayTask"); -// Constructor mConstructor = clazz.getConstructor(Activity.class); -// mPayTask = mConstructor.newInstance(mActivity); -// } -// final PayTask task = (PayTask) mPayTask; -// boolean isIntercepted = task.payInterceptorWithUrl(url, true, new H5PayCallback() { -// @Override -// public void onPayResult(final H5PayResultModel result) { -// final String url = result.getReturnUrl(); -// if (!TextUtils.isEmpty(url)) { -// AgentWebUtils.runInUiThread(new Runnable() { -// @Override -// public void run() { -// view.loadUrl(url); -// } -// }); -// } -// } -// }); -// if (isIntercepted) { -// LogUtils.i(TAG, "alipay-isIntercepted:" + isIntercepted + " url:" + url); -// } -// return isIntercepted; -// } catch (Throwable ignore) { -// if (AgentWebConfig.DEBUG) { -// ignore.printStackTrace(); -// } -// } - return false; - } - - - private boolean handleCommonLink(String url) { - if (url.startsWith(WebView.SCHEME_TEL) - || url.startsWith(SCHEME_SMS) - || url.startsWith(WebView.SCHEME_MAILTO) - || url.startsWith(WebView.SCHEME_GEO)) { - try { - Activity mActivity = null; - if ((mActivity = mWeakReference.get()) == null) { - return false; - } - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - mActivity.startActivity(intent); - } catch (ActivityNotFoundException ignored) { - if (AgentWebConfig.DEBUG) { - ignored.printStackTrace(); - } - } - return true; - } - return false; - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - - if (!mWaittingFinishSet.contains(url)) { - mWaittingFinishSet.add(url); - } - super.onPageStarted(view, url, favicon); - - } - - - /** - * MainFrame Error - * - * @param view - * @param errorCode - * @param description - * @param failingUrl - */ - @Override - public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - - if (AgentWebUtils.isOverriedMethod(mWebViewClient, "onReceivedError", ANDROID_WEBVIEWCLIENT_PATH + ".onReceivedError", WebView.class, int.class, String.class, String.class)) { - super.onReceivedError(view, errorCode, description, failingUrl); -// return; - } - LogUtils.i(TAG, "onReceivedError:" + description + " CODE:" + errorCode); - onMainFrameError(view, errorCode, description, failingUrl); - } - - - @Override - public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { - - if (AgentWebUtils.isOverriedMethod(mWebViewClient, "onReceivedError", ANDROID_WEBVIEWCLIENT_PATH + ".onReceivedError", WebView.class, WebResourceRequest.class, WebResourceError.class)) { - super.onReceivedError(view, request, error); -// return; - } - if (request.isForMainFrame()) { - onMainFrameError(view, - error.getErrorCode(), error.getDescription().toString(), - request.getUrl().toString()); - } - LogUtils.i(TAG, "onReceivedError:" + error.toString()); - } - - private void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { - mErrorUrlsSet.add(failingUrl); - // 下面逻辑判断开发者是否重写了 onMainFrameError 方法 , 优先交给开发者处理 - if (this.mWebViewClient != null && webClientHelper) { - Method mMethod = this.onMainFrameErrorMethod; - if (mMethod != null || (this.onMainFrameErrorMethod = mMethod = AgentWebUtils.isExistMethod(mWebViewClient, "onMainFrameError", AbsAgentWebUIController.class, WebView.class, int.class, String.class, String.class)) != null) { - try { - mMethod.invoke(this.mWebViewClient, mAgentWebUIController.get(), view, errorCode, description, failingUrl); - } catch (Throwable ignore) { - if (LogUtils.isDebug()) { - ignore.printStackTrace(); - } - } - return; - } - } - if (mAgentWebUIController.get() != null) { - mAgentWebUIController.get().onMainFrameError(view, errorCode, description, failingUrl); - } + /** + * Activity's WeakReference + */ + private WeakReference mWeakReference = null; + /** + * 缩放 + */ + private static final int CONSTANTS_ABNORMAL_BIG = 7; + /** + * WebViewClient + */ + private WebViewClient mWebViewClient; + /** + * mWebClientHelper + */ + private boolean webClientHelper = true; + /** + * intent ' s scheme + */ + public static final String INTENT_SCHEME = "intent://"; + /** + * Wechat pay scheme ,用于唤醒微信支付 + */ + public static final String WEBCHAT_PAY_SCHEME = "weixin://wap/pay?"; + /** + * 支付宝 + */ + public static final String ALIPAYS_SCHEME = "alipays://"; + /** + * http scheme + */ + public static final String HTTP_SCHEME = "http://"; + /** + * https scheme + */ + public static final String HTTPS_SCHEME = "https://"; + /** + * true 表示当前应用内依赖了 alipay library , false 反之 + */ + private static final boolean HAS_ALIPAY_LIB; + /** + * WebViewClient's tag 用于打印 + */ + private static final String TAG = DefaultWebClient.class.getSimpleName(); + /** + * 直接打开其他页面 + */ + public static final int DERECT_OPEN_OTHER_PAGE = 1001; + /** + * 弹窗咨询用户是否前往其他页面 + */ + public static final int ASK_USER_OPEN_OTHER_PAGE = DERECT_OPEN_OTHER_PAGE >> 2; + /** + * 不允许打开其他页面 + */ + public static final int DISALLOW_OPEN_OTHER_APP = DERECT_OPEN_OTHER_PAGE >> 4; + /** + * 默认为咨询用户 + */ + private int mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE; + /** + * 是否拦截找不到相应页面的Url,默认拦截 + */ + private boolean mIsInterceptUnkownUrl = true; + /** + * AbsAgentWebUIController + */ + private WeakReference mAgentWebUIController = null; + /** + * WebView + */ + private WebView mWebView; + /** + * 弹窗回调 + */ + private Handler.Callback mCallback = null; + /** + * MainFrameErrorMethod + */ + private Method onMainFrameErrorMethod = null; + /** + * Alipay PayTask 对象 + */ + private Object mPayTask; + /** + * SMS scheme + */ + public static final String SCHEME_SMS = "sms:"; + /** + * 缓存当前出现错误的页面 + */ + private Set mErrorUrlsSet = new HashSet<>(); + /** + * 缓存等待加载完成的页面 onPageStart()执行之后 ,onPageFinished()执行之前 + */ + private Set mWaittingFinishSet = new HashSet<>(); + + static { + boolean tag = true; + try { + Class.forName("com.alipay.sdk.app.PayTask"); + } catch (Throwable ignore) { + tag = false; + } + HAS_ALIPAY_LIB = tag; + LogUtils.i(TAG, "HAS_ALIPAY_LIB:" + HAS_ALIPAY_LIB); + } + + + DefaultWebClient(Builder builder) { + super(builder.mClient); + this.mWebView = builder.mWebView; + this.mWebViewClient = builder.mClient; + mWeakReference = new WeakReference(builder.mActivity); + this.webClientHelper = builder.mWebClientHelper; + mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(builder.mWebView)); + mIsInterceptUnkownUrl = builder.mIsInterceptUnkownScheme; + if (builder.mUrlHandleWays <= 0) { + mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE; + } else { + mUrlHandleWays = builder.mUrlHandleWays; + } + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + String url = request.getUrl().toString(); + if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) { + return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url)); + } + if (!webClientHelper) { + return super.shouldOverrideUrlLoading(view, request); + } + if (handleCommonLink(url)) { + return true; + } + // intent + if (url.startsWith(INTENT_SCHEME)) { + handleIntentUrl(url); + LogUtils.i(TAG, "intent url "); + return true; + } + // 微信支付 + if (url.startsWith(WEBCHAT_PAY_SCHEME)) { + LogUtils.i(TAG, "lookup wechat to pay ~~"); + startActivity(url); + return true; + } + if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) { + LogUtils.i(TAG, "alipays url lookup alipay ~~ "); + return true; + } + if (queryActiviesNumber(url) > 0 && deepLink(url)) { + LogUtils.i(TAG, "intercept url:" + url); + return true; + } + if (mIsInterceptUnkownUrl) { + LogUtils.i(TAG, "intercept UnkownUrl :" + request.getUrl()); + return true; + } + return super.shouldOverrideUrlLoading(view, request); + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + return super.shouldInterceptRequest(view, url); + } + + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { + super.onReceivedHttpAuthRequest(view, handler, host, realm); + } + + private boolean deepLink(String url) { + switch (mUrlHandleWays) { + // 直接打开其他App + case DERECT_OPEN_OTHER_PAGE: + lookup(url); + return true; + // 咨询用户是否打开其他App + case ASK_USER_OPEN_OTHER_PAGE: + Activity mActivity = null; + if ((mActivity = mWeakReference.get()) == null) { + return false; + } + ResolveInfo resolveInfo = lookupResolveInfo(url); + if (null == resolveInfo) { + return false; + } + ActivityInfo activityInfo = resolveInfo.activityInfo; + LogUtils.e(TAG, "resolve package:" + resolveInfo.activityInfo.packageName + " app package:" + mActivity.getPackageName()); + if (activityInfo != null + && !TextUtils.isEmpty(activityInfo.packageName) + && activityInfo.packageName.equals(mActivity.getPackageName())) { + return lookup(url); + } + if (mAgentWebUIController.get() != null) { + mAgentWebUIController.get() + .onOpenPagePrompt(this.mWebView, + mWebView.getUrl(), + getCallback(url)); + } + return true; + // 默认不打开 + default: + return false; + } + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return super.shouldInterceptRequest(view, request); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) { + return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url)); + } + if (!webClientHelper) { + return false; + } + //电话 , 邮箱 , 短信 + if (handleCommonLink(url)) { + return true; + } + //Intent scheme + if (url.startsWith(INTENT_SCHEME)) { + handleIntentUrl(url); + return true; + } + //微信支付 + if (url.startsWith(WEBCHAT_PAY_SCHEME)) { + startActivity(url); + return true; + } + //支付宝 + if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) { + return true; + } + //打开url 相对应的页面 + if (queryActiviesNumber(url) > 0 && deepLink(url)) { + LogUtils.i(TAG, "intercept OtherAppScheme"); + return true; + } + // 手机里面没有页面能匹配到该链接 ,拦截下来。 + if (mIsInterceptUnkownUrl) { + LogUtils.i(TAG, "intercept InterceptUnkownScheme : " + url); + return true; + } + return super.shouldOverrideUrlLoading(view, url); + } + + + private int queryActiviesNumber(String url) { + try { + if (mWeakReference.get() == null) { + return 0; + } + Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + PackageManager mPackageManager = mWeakReference.get().getPackageManager(); + List mResolveInfos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + return mResolveInfos == null ? 0 : mResolveInfos.size(); + } catch (URISyntaxException ignore) { + if (LogUtils.isDebug()) { + ignore.printStackTrace(); + } + return 0; + } + } + + private void handleIntentUrl(String intentUrl) { + try { + Intent intent = null; + if (TextUtils.isEmpty(intentUrl) || !intentUrl.startsWith(INTENT_SCHEME)) { + return; + } + if (lookup(intentUrl)) { + return; + } + } catch (Throwable e) { + if (LogUtils.isDebug()) { + e.printStackTrace(); + } + } + } + + + private ResolveInfo lookupResolveInfo(String url) { + try { + Intent intent; + Activity mActivity = null; + if ((mActivity = mWeakReference.get()) == null) { + return null; + } + PackageManager packageManager = mActivity.getPackageManager(); + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + return info; + } catch (Throwable ignore) { + if (LogUtils.isDebug()) { + ignore.printStackTrace(); + } + } + return null; + } + + private boolean lookup(String url) { + try { + Intent intent; + Activity mActivity = null; + if ((mActivity = mWeakReference.get()) == null) { + return true; + } + PackageManager packageManager = mActivity.getPackageManager(); + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + // 跳到该应用 + if (info != null) { + mActivity.startActivity(intent); + return true; + } + } catch (Throwable ignore) { + if (LogUtils.isDebug()) { + ignore.printStackTrace(); + } + } + return false; + } + + private boolean isAlipay(final WebView view, String url) { + try { + Activity mActivity = null; + if ((mActivity = mWeakReference.get()) == null) { + return false; + } + /** + * 推荐采用的新的二合一接口(payInterceptorWithUrl),只需调用一次 + */ + if (mPayTask == null) { + Class clazz = Class.forName("com.alipay.sdk.app.PayTask"); + Constructor mConstructor = clazz.getConstructor(Activity.class); + mPayTask = mConstructor.newInstance(mActivity); + } + final PayTask task = (PayTask) mPayTask; + boolean isIntercepted = task.payInterceptorWithUrl(url, true, new H5PayCallback() { + @Override + public void onPayResult(final H5PayResultModel result) { + final String url = result.getReturnUrl(); + if (!TextUtils.isEmpty(url)) { + AgentWebUtils.runInUiThread(new Runnable() { + @Override + public void run() { + view.loadUrl(url); + } + }); + } + } + }); + if (isIntercepted) { + LogUtils.i(TAG, "alipay-isIntercepted:" + isIntercepted + " url:" + url); + } + return isIntercepted; + } catch (Throwable ignore) { + if (AgentWebConfig.DEBUG) { + ignore.printStackTrace(); + } + } + return false; + } + + + private boolean handleCommonLink(String url) { + if (url.startsWith(WebView.SCHEME_TEL) + || url.startsWith(SCHEME_SMS) + || url.startsWith(WebView.SCHEME_MAILTO) + || url.startsWith(WebView.SCHEME_GEO)) { + try { + Activity mActivity = null; + if ((mActivity = mWeakReference.get()) == null) { + return false; + } + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + mActivity.startActivity(intent); + } catch (ActivityNotFoundException ignored) { + if (AgentWebConfig.DEBUG) { + ignored.printStackTrace(); + } + } + return true; + } + return false; + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (!mWaittingFinishSet.contains(url)) { + mWaittingFinishSet.add(url); + } + super.onPageStarted(view, url, favicon); + + } + + + /** + * MainFrame Error + * + * @param view + * @param errorCode + * @param description + * @param failingUrl + */ + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + LogUtils.i(TAG, "onReceivedError:" + description + " CODE:" + errorCode); + onMainFrameError(view, errorCode, description, failingUrl); + } + + + @TargetApi(Build.VERSION_CODES.M) + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + if (request.isForMainFrame()) { + onMainFrameError(view, + error.getErrorCode(), error.getDescription().toString(), + request.getUrl().toString()); + } + LogUtils.i(TAG, "onReceivedError:" + error.getDescription() + " code:" + error.getErrorCode()); + } + + private void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { + mErrorUrlsSet.add(failingUrl); + // 下面逻辑判断开发者是否重写了 onMainFrameError 方法 , 优先交给开发者处理 + if (this.mWebViewClient != null && webClientHelper) { + Method mMethod = this.onMainFrameErrorMethod; + if (mMethod != null || (this.onMainFrameErrorMethod = mMethod = AgentWebUtils.isExistMethod(mWebViewClient, "onMainFrameError", AbsAgentWebUIController.class, WebView.class, int.class, String.class, String.class)) != null) { + try { + mMethod.invoke(this.mWebViewClient, mAgentWebUIController.get(), view, errorCode, description, failingUrl); + } catch (Throwable ignore) { + if (LogUtils.isDebug()) { + ignore.printStackTrace(); + } + } + return; + } + } + if (mAgentWebUIController.get() != null) { + mAgentWebUIController.get().onMainFrameError(view, errorCode, description, failingUrl); + } // this.mWebView.setVisibility(View.GONE); - } - - - @Override - public void onPageFinished(WebView view, String url) { - - if (!mErrorUrlsSet.contains(url) && mWaittingFinishSet.contains(url)) { - if (mAgentWebUIController.get() != null) { - mAgentWebUIController.get().onShowMainFrame(); - } - } else { - view.setVisibility(View.VISIBLE); - } - if (mWaittingFinishSet.contains(url)) { - mWaittingFinishSet.remove(url); - } - if (!mErrorUrlsSet.isEmpty()) { - mErrorUrlsSet.clear(); - } - super.onPageFinished(view, url); - - } - - - @Override - public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { - return super.shouldOverrideKeyEvent(view, event); - } - - - private void startActivity(String url) { - - - try { - - if (mWeakReference.get() == null) { - return; - } - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - mWeakReference.get().startActivity(intent); - - } catch (Exception e) { - if (LogUtils.isDebug()) { - e.printStackTrace(); - } - } - - - } - - - @Override - public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { - super.onReceivedHttpError(view, request, errorResponse); - } - - @Override - public void onScaleChanged(WebView view, float oldScale, float newScale) { - - - if (AgentWebUtils.isOverriedMethod(mWebViewClient, "onScaleChanged", ANDROID_WEBVIEWCLIENT_PATH + ".onScaleChanged", WebView.class, float.class, float.class)) { - super.onScaleChanged(view, oldScale, newScale); - return; - } - - LogUtils.i(TAG, "onScaleChanged:" + oldScale + " n:" + newScale); - if (newScale - oldScale > CONSTANTS_ABNORMAL_BIG) { - view.setInitialScale((int) (oldScale / newScale * 100)); - } - - } - - - private Handler.Callback getCallback(final String url) { - if (this.mCallback != null) { - return this.mCallback; - } - return this.mCallback = new Handler.Callback() { - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case 1: - lookup(url); - break; - default: - return true; - } - return true; - } - }; - } - - - public static Builder createBuilder() { - return new Builder(); - } - - public static class Builder { - - private Activity mActivity; - private WebViewClient mClient; - private boolean mWebClientHelper; - private PermissionInterceptor mPermissionInterceptor; - private WebView mWebView; - private boolean mIsInterceptUnkownScheme; - private int mUrlHandleWays; - - public Builder setActivity(Activity activity) { - this.mActivity = activity; - return this; - } - - public Builder setClient(WebViewClient client) { - this.mClient = client; - return this; - } - - public Builder setWebClientHelper(boolean webClientHelper) { - this.mWebClientHelper = webClientHelper; - return this; - } - - public Builder setPermissionInterceptor(PermissionInterceptor permissionInterceptor) { - this.mPermissionInterceptor = permissionInterceptor; - return this; - } - - public Builder setWebView(WebView webView) { - this.mWebView = webView; - return this; - } - - public Builder setInterceptUnkownUrl(boolean interceptUnkownScheme) { - this.mIsInterceptUnkownScheme = interceptUnkownScheme; - return this; - } - - public Builder setUrlHandleWays(int urlHandleWays) { - this.mUrlHandleWays = urlHandleWays; - return this; - } - - public DefaultWebClient build() { - return new DefaultWebClient(this); - } - } - - public enum OpenOtherPageWays { - DERECT(DefaultWebClient.DERECT_OPEN_OTHER_PAGE), ASK(DefaultWebClient.ASK_USER_OPEN_OTHER_PAGE), DISALLOW(DefaultWebClient.DISALLOW_OPEN_OTHER_APP); - int code; - - OpenOtherPageWays(int code) { - this.code = code; - } - } + } + + + @Override + public void onPageFinished(WebView view, String url) { + if (!mErrorUrlsSet.contains(url) && mWaittingFinishSet.contains(url)) { + if (mAgentWebUIController.get() != null) { + mAgentWebUIController.get().onShowMainFrame(); + } + } else { + view.setVisibility(View.VISIBLE); + } + if (mWaittingFinishSet.contains(url)) { + mWaittingFinishSet.remove(url); + } + if (!mErrorUrlsSet.isEmpty()) { + mErrorUrlsSet.clear(); + } + super.onPageFinished(view, url); + } + + + @Override + public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { + return super.shouldOverrideKeyEvent(view, event); + } + + + private void startActivity(String url) { + try { + if (mWeakReference.get() == null) { + return; + } + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + mWeakReference.get().startActivity(intent); + + } catch (Exception e) { + if (LogUtils.isDebug()) { + e.printStackTrace(); + } + } + } + + @Override + public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + super.onReceivedHttpError(view, request, errorResponse); + } + + @Override + public void onScaleChanged(WebView view, float oldScale, float newScale) { + LogUtils.i(TAG, "onScaleChanged:" + oldScale + " n:" + newScale); + if (newScale - oldScale > CONSTANTS_ABNORMAL_BIG) { + view.setInitialScale((int) (oldScale / newScale * 100)); + } + } + + private Handler.Callback getCallback(final String url) { + if (this.mCallback != null) { + return this.mCallback; + } + return this.mCallback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case 1: + lookup(url); + break; + default: + return true; + } + return true; + } + }; + } + + + public static Builder createBuilder() { + return new Builder(); + } + + public static class Builder { + private Activity mActivity; + private WebViewClient mClient; + private boolean mWebClientHelper; + private PermissionInterceptor mPermissionInterceptor; + private WebView mWebView; + private boolean mIsInterceptUnkownScheme; + private int mUrlHandleWays; + + public Builder setActivity(Activity activity) { + this.mActivity = activity; + return this; + } + + public Builder setClient(WebViewClient client) { + this.mClient = client; + return this; + } + + public Builder setWebClientHelper(boolean webClientHelper) { + this.mWebClientHelper = webClientHelper; + return this; + } + + public Builder setPermissionInterceptor(PermissionInterceptor permissionInterceptor) { + this.mPermissionInterceptor = permissionInterceptor; + return this; + } + + public Builder setWebView(WebView webView) { + this.mWebView = webView; + return this; + } + + public Builder setInterceptUnkownUrl(boolean interceptUnkownScheme) { + this.mIsInterceptUnkownScheme = interceptUnkownScheme; + return this; + } + + public Builder setUrlHandleWays(int urlHandleWays) { + this.mUrlHandleWays = urlHandleWays; + return this; + } + + public DefaultWebClient build() { + return new DefaultWebClient(this); + } + } + + public static enum OpenOtherPageWays { + /** + * 直接打开跳转页 + */ + DERECT(DefaultWebClient.DERECT_OPEN_OTHER_PAGE), + /** + * 咨询用户是否打开 + */ + ASK(DefaultWebClient.ASK_USER_OPEN_OTHER_PAGE), + /** + * 禁止打开其他页面 + */ + DISALLOW(DefaultWebClient.DISALLOW_OPEN_OTHER_APP); + int code; + + OpenOtherPageWays(int code) { + this.code = code; + } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/DefaultWebCreator.java b/agentweb-core/src/main/java/com/just/agentweb/DefaultWebCreator.java index 0724001..75ad22c 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/DefaultWebCreator.java +++ b/agentweb-core/src/main/java/com/just/agentweb/DefaultWebCreator.java @@ -18,8 +18,6 @@ import android.app.Activity; import android.graphics.Color; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -27,12 +25,14 @@ import android.webkit.WebView; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * @author cenxiaozhong * @since 1.0.0 */ public class DefaultWebCreator implements WebCreator { - private Activity mActivity; private ViewGroup mViewGroup; private boolean mIsNeedDefaultProgress; @@ -52,18 +52,17 @@ public class DefaultWebCreator implements WebCreator { private View mTargetProgress; private static final String TAG = DefaultWebCreator.class.getSimpleName(); - /** - * 使用默认的进度条 - * - * @param activity - * @param viewGroup - * @param lp - * @param index - * @param color - * @param mHeight - * @param webView - * @param webLayout - */ + /** + * 使用默认的进度条 + * @param activity + * @param viewGroup + * @param lp + * @param index + * @param color + * @param mHeight + * @param webView + * @param webLayout + */ protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, @@ -83,16 +82,15 @@ protected DefaultWebCreator(@NonNull Activity activity, this.mIWebLayout = webLayout; } - /** - * 关闭进度条 - * - * @param activity - * @param viewGroup - * @param lp - * @param index - * @param webView - * @param webLayout - */ + /** + * 关闭进度条 + * @param activity + * @param viewGroup + * @param lp + * @param index + * @param webView + * @param webLayout + */ protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, int index, @Nullable WebView webView, IWebLayout webLayout) { this.mActivity = activity; this.mViewGroup = viewGroup; @@ -105,7 +103,6 @@ protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup view /** * 自定义Indicator - * * @param activity * @param viewGroup * @param lp @@ -145,8 +142,6 @@ public void setTargetProgress(View targetProgress) { @Override public DefaultWebCreator create() { - - if (mIsCreated) { return this; } @@ -175,13 +170,12 @@ public FrameLayout getWebParentLayout() { return mFrameLayout; } - private ViewGroup createLayout() { Activity mActivity = this.mActivity; WebParentLayout mFrameLayout = new WebParentLayout(mActivity); mFrameLayout.setId(R.id.web_parent_layout_id); mFrameLayout.setBackgroundColor(Color.WHITE); - View target = mIWebLayout == null ? (this.mWebView = createWebView()) : webLayout(); + View target = mIWebLayout == null ? (this.mWebView = (WebView) createWebView()) : webLayout(); FrameLayout.LayoutParams mLayoutParams = new FrameLayout.LayoutParams(-1, -1); mFrameLayout.addView(target, mLayoutParams); mFrameLayout.bindWebView(this.mWebView); @@ -207,11 +201,10 @@ private ViewGroup createLayout() { mFrameLayout.addView((View) (this.mBaseIndicatorSpec = mWebIndicator), lp); mWebIndicator.setVisibility(View.GONE); } else if (!mIsNeedDefaultProgress && mProgressView != null) { - mFrameLayout.addView((View) (this.mBaseIndicatorSpec = mProgressView), mProgressView.offerLayoutParams()); + mFrameLayout.addView((View) (this.mBaseIndicatorSpec = (BaseIndicatorSpec) mProgressView), mProgressView.offerLayoutParams()); mProgressView.setVisibility(View.GONE); } return mFrameLayout; - } @@ -221,17 +214,14 @@ private View webLayout() { mWebView = createWebView(); mIWebLayout.getLayout().addView(mWebView, -1, -1); LogUtils.i(TAG, "add webview"); - } else { AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_CUSTOM_TYPE; } this.mWebView = mWebView; return mIWebLayout.getLayout(); - } private WebView createWebView() { - WebView mWebView = null; if (this.mWebView != null) { mWebView = this.mWebView; @@ -240,10 +230,9 @@ private WebView createWebView() { mWebView = new AgentWebView(mActivity); AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE; } else { - mWebView = new WebView(mActivity); + mWebView = new LollipopFixedWebView(mActivity); AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_DEFAULT_TYPE; } - return mWebView; } diff --git a/agentweb-core/src/main/java/com/just/agentweb/DefaultWebLifeCycleImpl.java b/agentweb-core/src/main/java/com/just/agentweb/DefaultWebLifeCycleImpl.java index 963dfc0..a3cc047 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/DefaultWebLifeCycleImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/DefaultWebLifeCycleImpl.java @@ -26,7 +26,6 @@ */ public class DefaultWebLifeCycleImpl implements WebLifeCycle { private WebView mWebView; - DefaultWebLifeCycleImpl(WebView webView) { this.mWebView = webView; } @@ -34,22 +33,17 @@ public class DefaultWebLifeCycleImpl implements WebLifeCycle { @Override public void onResume() { if (this.mWebView != null) { - - if (Build.VERSION.SDK_INT >= 11) { + if (Build.VERSION.SDK_INT >= 11){ this.mWebView.onResume(); } this.mWebView.resumeTimers(); } - - } @Override public void onPause() { - if (this.mWebView != null) { - - if (Build.VERSION.SDK_INT >= 11) { + if (Build.VERSION.SDK_INT >= 11){ this.mWebView.onPause(); } this.mWebView.pauseTimers(); @@ -58,11 +52,9 @@ public void onPause() { @Override public void onDestroy() { - - if (this.mWebView != null) { + if(this.mWebView!=null){ this.mWebView.resumeTimers(); } AgentWebUtils.clearWebView(this.mWebView); - } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/EventHandlerImpl.java b/agentweb-core/src/main/java/com/just/agentweb/EventHandlerImpl.java index 10a9fa6..250dd3d 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/EventHandlerImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/EventHandlerImpl.java @@ -20,43 +20,45 @@ import android.webkit.WebView; /** - * IEventHandler 对事件的处理,主要是针对视频状态进行了处理 , 如果当前状态为 视频状态 则先退出视频。 + * IEventHandler 对事件的处理,主要是针对 + * 视屏状态进行了处理 , 如果当前状态为 视频状态 + * 则先退出视频。 * * @author cenxiaozhong * @date 2017/6/3 * @since 2.0.0 */ public class EventHandlerImpl implements IEventHandler { - private WebView mWebView; - private EventInterceptor mEventInterceptor; - - public static final EventHandlerImpl getInstantce(WebView view, EventInterceptor eventInterceptor) { - return new EventHandlerImpl(view, eventInterceptor); - } - - public EventHandlerImpl(WebView webView, EventInterceptor eventInterceptor) { - this.mWebView = webView; - this.mEventInterceptor = eventInterceptor; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - return back(); - } - return false; - } - - @Override - public boolean back() { - if (this.mEventInterceptor != null && this.mEventInterceptor.event()) { - return true; - } - if (mWebView != null && mWebView.canGoBack()) { - mWebView.goBack(); - return true; - } - return false; - } + private WebView mWebView; + private EventInterceptor mEventInterceptor; + + public static final EventHandlerImpl getInstantce(WebView view, EventInterceptor eventInterceptor) { + return new EventHandlerImpl(view, eventInterceptor); + } + + public EventHandlerImpl(WebView webView, EventInterceptor eventInterceptor) { + this.mWebView = webView; + this.mEventInterceptor = eventInterceptor; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return back(); + } + return false; + } + + @Override + public boolean back() { + if (this.mEventInterceptor != null && this.mEventInterceptor.event()) { + return true; + } + if (mWebView != null && mWebView.canGoBack()) { + mWebView.goBack(); + return true; + } + return false; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/HookManager.java b/agentweb-core/src/main/java/com/just/agentweb/HookManager.java index cf6277e..aa5541c 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/HookManager.java +++ b/agentweb-core/src/main/java/com/just/agentweb/HookManager.java @@ -23,14 +23,11 @@ */ public class HookManager { - public static AgentWeb hookAgentWeb(AgentWeb agentWeb, AgentWeb.AgentBuilder agentBuilder) { return agentWeb; } - public static boolean permissionHook(String url, String[] permissions) { + public static boolean permissionHook(String url,String[]permissions){ return true; } - - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/HttpHeaders.java b/agentweb-core/src/main/java/com/just/agentweb/HttpHeaders.java index 0fa314b..9e015f6 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/HttpHeaders.java +++ b/agentweb-core/src/main/java/com/just/agentweb/HttpHeaders.java @@ -16,7 +16,10 @@ package com.just.agentweb; -import android.support.v4.util.ArrayMap; +import android.net.Uri; +import android.text.TextUtils; + +import androidx.collection.ArrayMap; import java.util.Map; @@ -26,36 +29,84 @@ * @date 2017/7/5 * @since 2.0.0 */ -@Deprecated public class HttpHeaders { - - public static HttpHeaders create() { return new HttpHeaders(); } - private Map mHeaders = null; + private final Map> mHeaders; HttpHeaders() { - mHeaders = new ArrayMap<>(); + mHeaders = new ArrayMap>(); } - public Map getHeaders() { - return mHeaders; + public Map getHeaders(String url) { + String subUrl = subBaseUrl(url); + if (mHeaders.get(subUrl) == null) { + Map headers = new ArrayMap<>(); + mHeaders.put(subUrl, headers); + return headers; + } + return mHeaders.get(subUrl); } - public void additionalHttpHeader(String k, String v) { - mHeaders.put(k, v); + public void additionalHttpHeader(String url, String k, String v) { + if (null == url) { + return; + } + url = subBaseUrl(url); + Map> mHeaders = getHeaders(); + Map headersMap = mHeaders.get(subBaseUrl(url)); + if (null == headersMap) { + headersMap = new ArrayMap<>(); + } + headersMap.put(k, v); + mHeaders.put(url, headersMap); } - public void removeHttpHeader(String k) { - mHeaders.remove(k); + + public void additionalHttpHeaders(String url, Map headers) { + if (null == url) { + return; + } + String subUrl = subBaseUrl(url); + Map> mHeaders = getHeaders(); + Map headersMap = headers; + if (null == headersMap) { + headersMap = new ArrayMap<>(); + } + mHeaders.put(subUrl, headersMap); } - public boolean isEmptyHeaders() { - return mHeaders == null || mHeaders.isEmpty(); + public void removeHttpHeader(String url, String k) { + if (null == url) { + return; + } + String subUrl = subBaseUrl(url); + Map> mHeaders = getHeaders(); + Map headersMap = mHeaders.get(subUrl); + if (null != headersMap) { + headersMap.remove(k); + } } + public boolean isEmptyHeaders(String url) { + url = subBaseUrl(url); + Map heads = getHeaders(url); + return heads == null || heads.isEmpty(); + } + + public Map> getHeaders() { + return this.mHeaders; + } + + private String subBaseUrl(String originUrl) { + if (TextUtils.isEmpty(originUrl)) { + return originUrl; + } + Uri originUri = Uri.parse(originUrl); + return originUri.getScheme() + "://" + originUri.getAuthority(); + } @Override public String toString() { diff --git a/agentweb-core/src/main/java/com/just/agentweb/IEventHandler.java b/agentweb-core/src/main/java/com/just/agentweb/IEventHandler.java index c9337c4..ea05bb3 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/IEventHandler.java +++ b/agentweb-core/src/main/java/com/just/agentweb/IEventHandler.java @@ -26,6 +26,5 @@ public interface IEventHandler { boolean onKeyDown(int keyCode, KeyEvent event); - boolean back(); } diff --git a/agentweb-core/src/main/java/com/just/agentweb/IWebLayout.java b/agentweb-core/src/main/java/com/just/agentweb/IWebLayout.java index 8c4d6c7..18464f7 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/IWebLayout.java +++ b/agentweb-core/src/main/java/com/just/agentweb/IWebLayout.java @@ -16,30 +16,32 @@ package com.just.agentweb; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.view.ViewGroup; import android.webkit.WebView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * Created by cenxiaozhong on 2017/7/1. */ - /** * @author cenxiaozhong * @date 2017/7/1 * @update 4.0.0 * @since 1.0.0 */ -public interface IWebLayout { +public interface IWebLayout { /** + * * @return WebView 的父控件 */ @NonNull V getLayout(); /** + * * @return 返回 WebView 或 WebView 的子View ,返回null AgentWeb 内部会创建适当 WebView */ @Nullable diff --git a/agentweb-core/src/main/java/com/just/agentweb/IndicatorController.java b/agentweb-core/src/main/java/com/just/agentweb/IndicatorController.java index 81e6615..1cdf813 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/IndicatorController.java +++ b/agentweb-core/src/main/java/com/just/agentweb/IndicatorController.java @@ -17,7 +17,6 @@ package com.just.agentweb; import android.webkit.WebView; - /** * @author cenxiaozhong * @update 4.0.0 diff --git a/agentweb-core/src/main/java/com/just/agentweb/IndicatorHandler.java b/agentweb-core/src/main/java/com/just/agentweb/IndicatorHandler.java index 5ab9de6..bbc58e1 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/IndicatorHandler.java +++ b/agentweb-core/src/main/java/com/just/agentweb/IndicatorHandler.java @@ -24,65 +24,65 @@ * @since 1.0.0 */ public class IndicatorHandler implements IndicatorController { - private BaseIndicatorSpec mBaseIndicatorSpec; - - @Override - public void progress(WebView v, int newProgress) { - - if (newProgress == 0) { - reset(); - } else if (newProgress > 0 && newProgress <= 10) { - showIndicator(); - } else if (newProgress > 10 && newProgress < 95) { - setProgress(newProgress); - } else { - setProgress(newProgress); - finish(); - } - - } - - @Override - public BaseIndicatorSpec offerIndicator() { - return this.mBaseIndicatorSpec; - } - - public void reset() { - - if (mBaseIndicatorSpec != null) { - mBaseIndicatorSpec.reset(); - } - } - - @Override - public void finish() { - if (mBaseIndicatorSpec != null) { - mBaseIndicatorSpec.hide(); - } - } - - @Override - public void setProgress(int n) { - if (mBaseIndicatorSpec != null) { - mBaseIndicatorSpec.setProgress(n); - } - } - - @Override - public void showIndicator() { - - if (mBaseIndicatorSpec != null) { - mBaseIndicatorSpec.show(); - } - } - - static IndicatorHandler getInstance() { - return new IndicatorHandler(); - } - - - IndicatorHandler inJectIndicator(BaseIndicatorSpec baseIndicatorSpec) { - this.mBaseIndicatorSpec = baseIndicatorSpec; - return this; - } + private BaseIndicatorSpec mBaseIndicatorSpec; + + @Override + public void progress(WebView v, int newProgress) { + + if (newProgress == 0) { + reset(); + } else if (newProgress > 0 && newProgress <= 10) { + showIndicator(); + } else if (newProgress > 10 && newProgress < 95) { + setProgress(newProgress); + } else { + setProgress(newProgress); + finish(); + } + + } + + @Override + public BaseIndicatorSpec offerIndicator() { + return this.mBaseIndicatorSpec; + } + + public void reset() { + + if (mBaseIndicatorSpec != null) { + mBaseIndicatorSpec.reset(); + } + } + + @Override + public void finish() { + if (mBaseIndicatorSpec != null) { + mBaseIndicatorSpec.hide(); + } + } + + @Override + public void setProgress(int n) { + if (mBaseIndicatorSpec != null) { + mBaseIndicatorSpec.setProgress(n); + } + } + + @Override + public void showIndicator() { + + if (mBaseIndicatorSpec != null) { + mBaseIndicatorSpec.show(); + } + } + + static IndicatorHandler getInstance() { + return new IndicatorHandler(); + } + + + IndicatorHandler inJectIndicator(BaseIndicatorSpec baseIndicatorSpec) { + this.mBaseIndicatorSpec = baseIndicatorSpec; + return this; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/JsAccessEntraceImpl.java b/agentweb-core/src/main/java/com/just/agentweb/JsAccessEntraceImpl.java index a5f0b40..d57066c 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/JsAccessEntraceImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/JsAccessEntraceImpl.java @@ -32,7 +32,6 @@ public class JsAccessEntraceImpl extends BaseJsAccessEntrace { private WebView mWebView; private Handler mHandler = new Handler(Looper.getMainLooper()); - public static JsAccessEntraceImpl getInstance(WebView webView) { return new JsAccessEntraceImpl(webView); } @@ -42,8 +41,7 @@ private JsAccessEntraceImpl(WebView webView) { this.mWebView = webView; } - - private void callSafeCallJs(final String s, final ValueCallback valueCallback) { + private void safeCallJs(final String s, final ValueCallback valueCallback) { mHandler.post(new Runnable() { @Override public void run() { @@ -55,12 +53,10 @@ public void run() { @Override public void callJs(String params, final ValueCallback callback) { if (Thread.currentThread() != Looper.getMainLooper().getThread()) { - callSafeCallJs(params, callback); + safeCallJs(params, callback); return; } - - super.callJs(params, callback); - + super.callJs(params,callback); } diff --git a/agentweb-core/src/main/java/com/just/agentweb/JsBaseInterfaceHolder.java b/agentweb-core/src/main/java/com/just/agentweb/JsBaseInterfaceHolder.java index 9ecfb20..e8eb2bd 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/JsBaseInterfaceHolder.java +++ b/agentweb-core/src/main/java/com/just/agentweb/JsBaseInterfaceHolder.java @@ -29,49 +29,42 @@ */ public abstract class JsBaseInterfaceHolder implements JsInterfaceHolder { - private AgentWeb.SecurityType mSecurityType; - - protected JsBaseInterfaceHolder(AgentWeb.SecurityType securityType) { - this.mSecurityType = securityType; - } - - @Override - public boolean checkObject(Object v) { - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { - return true; - } - if (AgentWebConfig.WEBVIEW_TYPE == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE) { - return true; - } - boolean tag = false; - Class clazz = v.getClass(); - - Method[] mMethods = clazz.getMethods(); - - for (Method mMethod : mMethods) { - - Annotation[] mAnnotations = mMethod.getAnnotations(); - - for (Annotation mAnnotation : mAnnotations) { - - if (mAnnotation instanceof JavascriptInterface) { - tag = true; - break; - } - - } - if (tag) { - break; - } - } - - return tag; - } - - protected boolean checkSecurity() { - return mSecurityType != AgentWeb.SecurityType.STRICT_CHECK || (AgentWebConfig.WEBVIEW_TYPE == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE || Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1); - } - + private AgentWeb.SecurityType mSecurityType; + + protected JsBaseInterfaceHolder(AgentWeb.SecurityType securityType) { + this.mSecurityType = securityType; + } + + @Override + public boolean checkObject(Object v) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + return true; + } + if (AgentWebConfig.WEBVIEW_TYPE == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE) { + return true; + } + boolean tag = false; + Class clazz = v.getClass(); + Method[] mMethods = clazz.getMethods(); + for (Method mMethod : mMethods) { + Annotation[] mAnnotations = mMethod.getAnnotations(); + for (Annotation mAnnotation : mAnnotations) { + if (mAnnotation instanceof JavascriptInterface) { + tag = true; + break; + } + } + if (tag) { + break; + } + } + return tag; + } + + protected boolean checkSecurity() { + return mSecurityType != AgentWeb.SecurityType.STRICT_CHECK + ? true : AgentWebConfig.WEBVIEW_TYPE == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE + ? true : Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/JsCallJava.java b/agentweb-core/src/main/java/com/just/agentweb/JsCallJava.java index 62421b2..de1b00b 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/JsCallJava.java +++ b/agentweb-core/src/main/java/com/just/agentweb/JsCallJava.java @@ -56,7 +56,7 @@ public JsCallJava(Object interfaceObj, String interfaceName) { sb.append(mInterfacedName); sb.append(" init begin\");var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};"); for (Method method : methods) { - Log.i("Info", "method:" + method); + Log.i("Info","method:"+method); String sign; if ((sign = genJavaMethodSign(method)) == null) { continue; diff --git a/agentweb-core/src/main/java/com/just/agentweb/JsCallback.java b/agentweb-core/src/main/java/com/just/agentweb/JsCallback.java index c7e12e2..887f910 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/JsCallback.java +++ b/agentweb-core/src/main/java/com/just/agentweb/JsCallback.java @@ -26,7 +26,6 @@ import org.json.JSONObject; import java.lang.ref.WeakReference; - public class JsCallback { private static final String CALLBACK_JS_FORMAT = "javascript:%s.callback(%d, %d %s);"; private int mIndex; @@ -44,11 +43,10 @@ public JsCallback(WebView view, String injectedName, int index) { /** * 向网页执行js回调; - * * @param args * @throws JsCallbackException */ - public void apply(Object... args) throws JsCallbackException { + public void apply (Object... args) throws JsCallbackException { if (mWebViewRef.get() == null) { throw new JsCallbackException("the WebView related to the JsCallback has been recycled"); } @@ -56,7 +54,7 @@ public void apply(Object... args) throws JsCallbackException { throw new JsCallbackException("the JsCallback isn't permanent,cannot be called more than once"); } StringBuilder sb = new StringBuilder(); - for (Object arg : args) { + for (Object arg : args){ sb.append(","); boolean isStrArg = arg instanceof String; // 有的接口将Json对象转换成了String返回,这里不能加双引号,否则网页会认为是String而不是JavaScript对象; @@ -79,7 +77,6 @@ public void apply(Object... args) throws JsCallbackException { /** * 是否是JSON(JavaScript Object Notation)对象; - * * @param obj * @return */ @@ -104,15 +101,14 @@ private boolean isJavaScriptObject(Object obj) { /** * 一般传入到Java方法的js function是一次性使用的,即在Java层jsCallback.apply(...)之后不能再发起回调了; * 如果需要传入的function能够在当前页面生命周期内多次使用,请在第一次apply前setPermanent(true); - * * @param value */ - public void setPermanent(boolean value) { + public void setPermanent (boolean value) { mIsPermanent = value ? 1 : 0; } public static class JsCallbackException extends Exception { - public JsCallbackException(String msg) { + public JsCallbackException (String msg) { super(msg); } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolderImpl.java b/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolderImpl.java index 0227171..acf9a8a 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolderImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolderImpl.java @@ -28,66 +28,57 @@ */ public class JsInterfaceHolderImpl extends JsBaseInterfaceHolder { - private static final String TAG = JsInterfaceHolderImpl.class.getSimpleName(); - private WebView mWebView; - private AgentWeb.SecurityType mSecurityType; - - static JsInterfaceHolderImpl getJsInterfaceHolder(WebView webView, AgentWeb.SecurityType securityType) { - - return new JsInterfaceHolderImpl(webView, securityType); - } - - - JsInterfaceHolderImpl(WebView webView, AgentWeb.SecurityType securityType) { - super(securityType); - this.mWebView = webView; - this.mSecurityType = securityType; - } - - @Override - public JsInterfaceHolder addJavaObjects(Map maps) { - - - if (!checkSecurity()) { - LogUtils.e(TAG, "The injected object is not safe, give up injection"); - return this; - } - - Set> sets = maps.entrySet(); - for (Map.Entry mEntry : sets) { - - Object v = mEntry.getValue(); - boolean t = checkObject(v); - if (!t) { - throw new JsInterfaceObjectException("This object has not offer method javascript to call ,please check addJavascriptInterface annotation was be added"); - } else { - addJavaObjectDirect(mEntry.getKey(), v); - } - } - - return this; - } - - @Override - public JsInterfaceHolder addJavaObject(String k, Object v) { - - if (!checkSecurity()) { - return this; - } - boolean t = checkObject(v); - if (!t) { - throw new JsInterfaceObjectException("this object has not offer method javascript to call , please check addJavascriptInterface annotation was be added"); - } else { - addJavaObjectDirect(k, v); - } - return this; - } - - private JsInterfaceHolder addJavaObjectDirect(String k, Object v) { - LogUtils.i(TAG, "k:" + k + " v:" + v); - this.mWebView.addJavascriptInterface(v, k); - return this; - } - + private static final String TAG = JsInterfaceHolderImpl.class.getSimpleName(); + private WebView mWebView; + private AgentWeb.SecurityType mSecurityType; + + static JsInterfaceHolderImpl getJsInterfaceHolder(WebView webView, AgentWeb.SecurityType securityType) { + return new JsInterfaceHolderImpl(webView, securityType); + } + + JsInterfaceHolderImpl(WebView webView, AgentWeb.SecurityType securityType) { + super(securityType); + this.mWebView = webView; + this.mSecurityType = securityType; + } + + @Override + public JsInterfaceHolder addJavaObjects(Map maps) { + if (!checkSecurity()) { + LogUtils.e(TAG, "The injected object is not safe, give up injection"); + return this; + } + Set> sets = maps.entrySet(); + for (Map.Entry mEntry : sets) { + Object v = mEntry.getValue(); + boolean t = checkObject(v); + if (!t) { + throw new JsInterfaceObjectException("This object has not offer method javascript to call ,please check addJavascriptInterface annotation was be added"); + } else { + addJavaObjectDirect(mEntry.getKey(), v); + } + } + return this; + } + + @Override + public JsInterfaceHolder addJavaObject(String k, Object v) { + if (!checkSecurity()) { + return this; + } + boolean t = checkObject(v); + if (!t) { + throw new JsInterfaceObjectException("this object has not offer method javascript to call , please check addJavascriptInterface annotation was be added"); + } else { + addJavaObjectDirect(k, v); + } + return this; + } + + private JsInterfaceHolder addJavaObjectDirect(String k, Object v) { + LogUtils.i(TAG, "k:" + k + " v:" + v); + this.mWebView.addJavascriptInterface(v, k); + return this; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceObjectException.java b/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceObjectException.java index f113611..0827272 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceObjectException.java +++ b/agentweb-core/src/main/java/com/just/agentweb/JsInterfaceObjectException.java @@ -23,7 +23,7 @@ * @since 1.0.0 */ public class JsInterfaceObjectException extends RuntimeException { - JsInterfaceObjectException(String msg) { + JsInterfaceObjectException(String msg){ super(msg); } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/LogUtils.java b/agentweb-core/src/main/java/com/just/agentweb/LogUtils.java index f362af0..1c53ce9 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/LogUtils.java +++ b/agentweb-core/src/main/java/com/just/agentweb/LogUtils.java @@ -25,7 +25,7 @@ */ public class LogUtils { - private static final String PREFIX = " agentweb - "; // + private static final String PREFIX = " agentweb - "; public static boolean isDebug() { return AgentWebConfig.DEBUG; @@ -33,14 +33,14 @@ public static boolean isDebug() { public static void i(String tag, String message) { - if (isDebug()) { + if (isDebug()){ Log.i(PREFIX.concat(tag), message); } } public static void v(String tag, String message) { - if (isDebug()) { + if (isDebug()){ Log.v(PREFIX.concat(tag), message); } @@ -60,7 +60,7 @@ public static void e(String tag, String msg, Throwable tr) { public static void e(String tag, String message) { - if (isDebug()) { + if (isDebug()){ Log.e(PREFIX.concat(tag), message); } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/LollipopFixedWebView.java b/agentweb-core/src/main/java/com/just/agentweb/LollipopFixedWebView.java new file mode 100644 index 0000000..4e06fb5 --- /dev/null +++ b/agentweb-core/src/main/java/com/just/agentweb/LollipopFixedWebView.java @@ -0,0 +1,44 @@ +package com.just.agentweb; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.webkit.WebView; + +/** + * 修复 Android 5.0 & 5.1 打开 WebView 闪退问题: + * 参阅 https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview + */ +@SuppressWarnings("unused") +public class LollipopFixedWebView extends WebView { + public LollipopFixedWebView(Context context) { + super(getFixedContext(context)); + } + + public LollipopFixedWebView(Context context, AttributeSet attrs) { + super(getFixedContext(context), attrs); + } + + public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(getFixedContext(context), attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(getFixedContext(context), attrs, defStyleAttr, defStyleRes); + } + + public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { + super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing); + } + + public static Context getFixedContext(Context context) { +// if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23) { +// // A void crashing on Android 5 and 6 (API level 21 to 23) +// return context.createConfigurationContext(new Configuration()); +// } + return context; + } + +} diff --git a/agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebClientBase.java b/agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebClientBase.java index 7b3d1aa..cf0de3e 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebClientBase.java +++ b/agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebClientBase.java @@ -25,7 +25,7 @@ */ public class MiddlewareWebClientBase extends WebViewClientDelegate { private MiddlewareWebClientBase mMiddleWrareWebClientBase; - private String TAG = this.getClass().getSimpleName(); + private static String TAG = MiddlewareWebClientBase.class.getSimpleName(); MiddlewareWebClientBase(MiddlewareWebClientBase client) { super(client); @@ -44,7 +44,6 @@ final MiddlewareWebClientBase next() { return this.mMiddleWrareWebClientBase; } - @Override final void setDelegate(WebViewClient delegate) { super.setDelegate(delegate); @@ -57,5 +56,4 @@ final MiddlewareWebClientBase enq(MiddlewareWebClientBase middleWrareWebClientBa return middleWrareWebClientBase; } - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/NestedScrollAgentWebView.java b/agentweb-core/src/main/java/com/just/agentweb/NestedScrollAgentWebView.java index 446c3bb..1046a19 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/NestedScrollAgentWebView.java +++ b/agentweb-core/src/main/java/com/just/agentweb/NestedScrollAgentWebView.java @@ -16,103 +16,142 @@ package com.just.agentweb; import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.NestedScrollingChild; -import android.support.v4.view.NestedScrollingChildHelper; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; import android.view.MotionEvent; +import androidx.core.view.NestedScrollingChild; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.ViewCompat; + /** * 结合CoordinatorLayout可以与Toolbar联动的webview - * * @author LeonDevLifeLog * @since 4.0.0 */ public class NestedScrollAgentWebView extends AgentWebView implements NestedScrollingChild { - private int mLastMotionY; - - private final int[] mScrollOffset = new int[2]; - private final int[] mScrollConsumed = new int[2]; - - private int mNestedYOffset; - - private NestedScrollingChildHelper mChildHelper; - + /*** + * 方法一 + * https://github.com/fashare2015/NestedScrollWebView + */ public NestedScrollAgentWebView(Context context) { super(context); - init(); + initView(); } - public NestedScrollAgentWebView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } + private static final int INVALID_POINTER = -1; - private void init() { + private void initView() { mChildHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); } - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = false; - MotionEvent trackedEvent = MotionEvent.obtain(event); + /** + * Position of the last motion event. + */ + private int mLastMotionY; - final int action = MotionEventCompat.getActionMasked(event); + /** + * ID of the active pointer. This is used to retain consistency during + * drags/flings if multiple pointers are used. + */ + private int mActivePointerId = INVALID_POINTER; - if (action == MotionEvent.ACTION_DOWN) { - mNestedYOffset = 0; - } + /** + * Used during scrolling to retrieve the new offset within the window. + */ + private final int[] mScrollOffset = new int[2]; + private final int[] mScrollConsumed = new int[2]; + private NestedScrollingChildHelper mChildHelper; + boolean mIsBeingDragged; - int y = (int) event.getY(); - event.offsetLocation(0, mNestedYOffset); + @Override + public boolean onTouchEvent(MotionEvent ev) { + final int actionMasked = ev.getActionMasked(); - switch (action) { + switch (actionMasked) { case MotionEvent.ACTION_DOWN: - mLastMotionY = y; + mIsBeingDragged = false; + + // Remember where the motion event started + mLastMotionY = (int) ev.getY(); +// downY = (int) ev.getY(); + + mActivePointerId = ev.getPointerId(0); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); - result = super.onTouchEvent(event); break; + case MotionEvent.ACTION_MOVE: - int deltaY = mLastMotionY - y; +// KLog.e("移动开始:"); + final int activePointerIndex = ev.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + break; + } +// if( !onlyVerticalMove(ev) ){ +// break; +// } + final int y = (int) ev.getY(activePointerIndex); + int deltaY = mLastMotionY - y; if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { deltaY -= mScrollConsumed[1]; - trackedEvent.offsetLocation(0, mScrollOffset[1]); - mNestedYOffset += mScrollOffset[1]; } - + // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; - int oldY = getScrollY(); - int newScrollY = Math.max(0, oldY + deltaY); - int dyConsumed = newScrollY - oldY; - int dyUnconsumed = deltaY - dyConsumed; - - if (dispatchNestedScroll(0, dyConsumed, 0, dyUnconsumed, mScrollOffset)) { + final int oldY = getScrollY(); + final int scrolledDeltaY = Math.max(0, oldY + deltaY) - oldY; + final int unconsumedY = deltaY - scrolledDeltaY; + if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { mLastMotionY -= mScrollOffset[1]; - trackedEvent.offsetLocation(0, mScrollOffset[1]); - mNestedYOffset += mScrollOffset[1]; } - - result = super.onTouchEvent(trackedEvent); - trackedEvent.recycle(); +// KLog.e("移动结束:"); break; - case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_UP: + mActivePointerId = INVALID_POINTER; + endDrag(); + break; case MotionEvent.ACTION_CANCEL: - stopNestedScroll(); - result = super.onTouchEvent(event); + mActivePointerId = INVALID_POINTER; + endDrag(); + break; + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = ev.getActionIndex(); + mLastMotionY = (int) ev.getY(index); + mActivePointerId = ev.getPointerId(index); + break; + } + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); + break; + default: break; } - return result; + return super.onTouchEvent(ev); + } + + private void endDrag() { + mIsBeingDragged = false; + stopNestedScroll(); } + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = ev.getActionIndex(); + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = (int) ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + } + } + + @Override public void setNestedScrollingEnabled(boolean enabled) { mChildHelper.setNestedScrollingEnabled(enabled); @@ -140,6 +179,7 @@ public boolean hasNestedScrollingParent() { @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { +// KLog.e("分配滚动事件:" + dxConsumed + " " + dyConsumed ); return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } diff --git a/agentweb-core/src/main/java/com/just/agentweb/PermissionInterceptor.java b/agentweb-core/src/main/java/com/just/agentweb/PermissionInterceptor.java index e88c0e9..d05ba9c 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/PermissionInterceptor.java +++ b/agentweb-core/src/main/java/com/just/agentweb/PermissionInterceptor.java @@ -21,7 +21,5 @@ * @since 3.0.0 */ public interface PermissionInterceptor { - boolean intercept(String url, String[] permissions, String action); - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/ProcessUtils.java b/agentweb-core/src/main/java/com/just/agentweb/ProcessUtils.java new file mode 100644 index 0000000..656fd84 --- /dev/null +++ b/agentweb-core/src/main/java/com/just/agentweb/ProcessUtils.java @@ -0,0 +1,78 @@ +package com.just.agentweb; + +import android.app.ActivityManager; +import android.app.Application; +import android.content.Context; +import android.text.TextUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +/** + * Adapted from com.blankj.utilcode.util.ProcessUtils#getCurrentProcessName + */ +class ProcessUtils { + + static String getCurrentProcessName(Context context) { + String name = getCurrentProcessNameByFile(); + if (!TextUtils.isEmpty(name)) return name; + name = getCurrentProcessNameByAms(context); + if (!TextUtils.isEmpty(name)) return name; + name = getCurrentProcessNameByReflect(context); + return name; + } + + private static String getCurrentProcessNameByFile() { + try { + File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline"); + BufferedReader mBufferedReader = new BufferedReader(new FileReader(file)); + String processName = mBufferedReader.readLine().trim(); + mBufferedReader.close(); + return processName; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + private static String getCurrentProcessNameByAms(Context context) { + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (am == null) return ""; + List info = am.getRunningAppProcesses(); + if (info == null || info.size() == 0) return ""; + int pid = android.os.Process.myPid(); + for (ActivityManager.RunningAppProcessInfo aInfo : info) { + if (aInfo.pid == pid) { + if (aInfo.processName != null) { + return aInfo.processName; + } + } + } + return ""; + } + + private static String getCurrentProcessNameByReflect(Context context) { + String processName = ""; + try { + Application app = (Application) context.getApplicationContext(); + Field loadedApkField = app.getClass().getField("mLoadedApk"); + loadedApkField.setAccessible(true); + Object loadedApk = loadedApkField.get(app); + + Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread"); + activityThreadField.setAccessible(true); + Object activityThread = activityThreadField.get(loadedApk); + + Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName"); + processName = (String) getProcessName.invoke(activityThread); + } catch (Exception e) { + e.printStackTrace(); + } + return processName; + } + +} diff --git a/agentweb-core/src/main/java/com/just/agentweb/Provider.java b/agentweb-core/src/main/java/com/just/agentweb/Provider.java index 4a89309..645f253 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/Provider.java +++ b/agentweb-core/src/main/java/com/just/agentweb/Provider.java @@ -22,7 +22,5 @@ * @since 1.0.0 */ public interface Provider { - - - T provide(); + T provide(); } diff --git a/agentweb-core/src/main/java/com/just/agentweb/QuickCallJs.java b/agentweb-core/src/main/java/com/just/agentweb/QuickCallJs.java index a62e981..0cedffe 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/QuickCallJs.java +++ b/agentweb-core/src/main/java/com/just/agentweb/QuickCallJs.java @@ -17,9 +17,10 @@ package com.just.agentweb; import android.os.Build; -import android.support.annotation.RequiresApi; import android.webkit.ValueCallback; +import androidx.annotation.RequiresApi; + /** * @author cenxiaozhong diff --git a/agentweb-core/src/main/java/com/just/agentweb/UrlCommonException.java b/agentweb-core/src/main/java/com/just/agentweb/UrlCommonException.java index 50b7651..1613ddb 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/UrlCommonException.java +++ b/agentweb-core/src/main/java/com/just/agentweb/UrlCommonException.java @@ -15,14 +15,13 @@ */ package com.just.agentweb; - /** * @author cenxiaozhong * @since 1.0.0 */ public class UrlCommonException extends RuntimeException { - public UrlCommonException() { + public UrlCommonException() { } public UrlCommonException(String msg) { diff --git a/agentweb-core/src/main/java/com/just/agentweb/UrlLoaderImpl.java b/agentweb-core/src/main/java/com/just/agentweb/UrlLoaderImpl.java index 162883f..f7233e4 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/UrlLoaderImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/UrlLoaderImpl.java @@ -27,147 +27,136 @@ * @since 2.0.0 */ public class UrlLoaderImpl implements IUrlLoader { - - - private Handler mHandler = null; - private WebView mWebView; - private HttpHeaders mHttpHeaders; - - UrlLoaderImpl(WebView webView, HttpHeaders httpHeaders) { - this.mWebView = webView; - if (this.mWebView == null) { - new NullPointerException("webview cannot be null ."); - } - - this.mHttpHeaders = httpHeaders; - mHandler = new Handler(Looper.getMainLooper()); - } - - private void safeLoadUrl(final String url) { - - mHandler.post(new Runnable() { - @Override - public void run() { - loadUrl(url); - } - }); - } - - private void safeReload() { - - mHandler.post(new Runnable() { - @Override - public void run() { - reload(); - } - }); - } - - @Override - public void loadUrl(String url) { - this.loadUrl(url, null); - } - - @Override - public void loadUrl(final String url, final Map headers) { - - if (!AgentWebUtils.isUIThread()) { - AgentWebUtils.runInUiThread(new Runnable() { - @Override - public void run() { - loadUrl(url, headers); - } - }); - } - if (headers == null || headers.isEmpty()) { - this.mWebView.loadUrl(url); - } else { - this.mWebView.loadUrl(url, headers); - } - } - - @Override - public void reload() { - if (!AgentWebUtils.isUIThread()) { - mHandler.post(new Runnable() { - @Override - public void run() { - reload(); - } - }); - return; - } - this.mWebView.reload(); - - - } - - @Override - public void loadData(final String data, final String mimeType, final String encoding) { - - if (!AgentWebUtils.isUIThread()) { - mHandler.post(new Runnable() { - @Override - public void run() { - loadData(data, mimeType, encoding); - } - }); - return; - } - this.mWebView.loadData(data, mimeType, encoding); - - } - - @Override - public void stopLoading() { - - if (!AgentWebUtils.isUIThread()) { - mHandler.post(new Runnable() { - @Override - public void run() { - stopLoading(); - } - }); - return; - } - this.mWebView.stopLoading(); - - } - - @Override - public void loadDataWithBaseURL(final String baseUrl, final String data, final String mimeType, final String encoding, final String historyUrl) { - - if (!AgentWebUtils.isUIThread()) { - mHandler.post(new Runnable() { - @Override - public void run() { - loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); - } - }); - return; - } - this.mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); - - } - - @Override - public void postUrl(final String url, final byte[] postData) { - - if (!AgentWebUtils.isUIThread()) { - mHandler.post(new Runnable() { - @Override - public void run() { - postUrl(url, postData); - } - }); - return; - } - - this.mWebView.postUrl(url, postData); - } - - @Override - public HttpHeaders getHttpHeaders() { - return this.mHttpHeaders == null ? this.mHttpHeaders = HttpHeaders.create() : this.mHttpHeaders; - } + private Handler mHandler = null; + private WebView mWebView; + private HttpHeaders mHttpHeaders; + public static final String TAG = UrlLoaderImpl.class.getSimpleName(); + + UrlLoaderImpl(WebView webView, HttpHeaders httpHeaders) { + this.mWebView = webView; + if (this.mWebView == null) { + new NullPointerException("webview cannot be null ."); + } + this.mHttpHeaders = httpHeaders; + if (this.mHttpHeaders == null) { + this.mHttpHeaders = HttpHeaders.create(); + } + mHandler = new Handler(Looper.getMainLooper()); + } + + private void safeLoadUrl(final String url) { + mHandler.post(new Runnable() { + @Override + public void run() { + loadUrl(url); + } + }); + } + + private void safeReload() { + mHandler.post(new Runnable() { + @Override + public void run() { + reload(); + } + }); + } + + @Override + public void loadUrl(String url) { + this.loadUrl(url, this.mHttpHeaders.getHeaders(url)); + } + + @Override + public void loadUrl(final String url, final Map headers) { + if (!AgentWebUtils.isUIThread()) { + AgentWebUtils.runInUiThread(new Runnable() { + @Override + public void run() { + loadUrl(url, headers); + } + }); + } + LogUtils.i(TAG, "loadUrl:" + url + " headers:" + headers); + if (headers == null || headers.isEmpty()) { + this.mWebView.loadUrl(url); + } else { + this.mWebView.loadUrl(url, headers); + } + } + + @Override + public void reload() { + if (!AgentWebUtils.isUIThread()) { + mHandler.post(new Runnable() { + @Override + public void run() { + reload(); + } + }); + return; + } + this.mWebView.reload(); + } + + @Override + public void loadData(final String data, final String mimeType, final String encoding) { + if (!AgentWebUtils.isUIThread()) { + mHandler.post(new Runnable() { + @Override + public void run() { + loadData(data, mimeType, encoding); + } + }); + return; + } + this.mWebView.loadData(data, mimeType, encoding); + } + + @Override + public void stopLoading() { + if (!AgentWebUtils.isUIThread()) { + mHandler.post(new Runnable() { + @Override + public void run() { + stopLoading(); + } + }); + return; + } + this.mWebView.stopLoading(); + } + + @Override + public void loadDataWithBaseURL(final String baseUrl, final String data, final String mimeType, final String encoding, final String historyUrl) { + if (!AgentWebUtils.isUIThread()) { + mHandler.post(new Runnable() { + @Override + public void run() { + loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + }); + return; + } + this.mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + + @Override + public void postUrl(final String url, final byte[] postData) { + if (!AgentWebUtils.isUIThread()) { + mHandler.post(new Runnable() { + @Override + public void run() { + postUrl(url, postData); + } + }); + return; + } + this.mWebView.postUrl(url, postData); + } + + @Override + public HttpHeaders getHttpHeaders() { + return this.mHttpHeaders == null ? this.mHttpHeaders = HttpHeaders.create() : this.mHttpHeaders; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/VideoImpl.java b/agentweb-core/src/main/java/com/just/agentweb/VideoImpl.java index 56da77a..dcd2c05 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/VideoImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/VideoImpl.java @@ -20,7 +20,6 @@ import android.content.pm.ActivityInfo; import android.graphics.Color; import android.os.Build; -import android.support.v4.util.Pair; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -29,6 +28,8 @@ import android.webkit.WebView; import android.widget.FrameLayout; +import androidx.core.util.Pair; + import java.util.HashSet; import java.util.Set; @@ -37,7 +38,6 @@ */ public class VideoImpl implements IVideo, EventInterceptor { - private Activity mActivity; private WebView mWebView; private static final String TAG = VideoImpl.class.getSimpleName(); @@ -50,10 +50,8 @@ public VideoImpl(Activity mActivity, WebView webView) { this.mActivity = mActivity; this.mWebView = webView; mFlags = new HashSet<>(); - } - @Override public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { Activity mActivity; @@ -61,8 +59,6 @@ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callb return; } mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - - Window mWindow = mActivity.getWindow(); Pair mPair = null; // 保存当前屏幕的状态 @@ -71,23 +67,18 @@ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callb mWindow.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mFlags.add(mPair); } - if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && (mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) == 0) { mPair = new Pair<>(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 0); mWindow.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); mFlags.add(mPair); } - - if (mMoiveView != null) { callback.onCustomViewHidden(); return; } - if (mWebView != null) { mWebView.setVisibility(View.GONE); } - if (mMoiveParentView == null) { FrameLayout mDecorView = (FrameLayout) mActivity.getWindow().getDecorView(); mMoiveParentView = new FrameLayout(mActivity); @@ -97,26 +88,22 @@ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callb this.mCallback = callback; mMoiveParentView.addView(this.mMoiveView = view); mMoiveParentView.setVisibility(View.VISIBLE); - } @Override public void onHideCustomView() { - if (mMoiveView == null) { return; } if (mActivity != null && mActivity.getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } - if (!mFlags.isEmpty()) { for (Pair mPair : mFlags) { mActivity.getWindow().setFlags(mPair.second, mPair.first); } mFlags.clear(); } - mMoiveView.setVisibility(View.GONE); if (mMoiveParentView != null && mMoiveView != null) { mMoiveParentView.removeView(mMoiveView); @@ -125,7 +112,6 @@ public void onHideCustomView() { if (mMoiveParentView != null) { mMoiveParentView.setVisibility(View.GONE); } - if (this.mCallback != null) { mCallback.onCustomViewHidden(); } @@ -133,7 +119,6 @@ public void onHideCustomView() { if (mWebView != null) { mWebView.setVisibility(View.VISIBLE); } - } @Override @@ -150,6 +135,5 @@ public boolean event() { } else { return false; } - } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebChromeClient.java b/agentweb-core/src/main/java/com/just/agentweb/WebChromeClient.java new file mode 100644 index 0000000..94da8b7 --- /dev/null +++ b/agentweb-core/src/main/java/com/just/agentweb/WebChromeClient.java @@ -0,0 +1,11 @@ +package com.just.agentweb; + +/** + * @author cenxiaozhong + * @date 2019/4/13 + * @since 1.0.0 + */ +public class WebChromeClient extends MiddlewareWebChromeBase{ + public WebChromeClient() { + } +} diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebChromeClientDelegate.java b/agentweb-core/src/main/java/com/just/agentweb/WebChromeClientDelegate.java index 1b8e51e..3ca9396 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebChromeClientDelegate.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebChromeClientDelegate.java @@ -20,7 +20,6 @@ import android.net.Uri; import android.os.Build; import android.os.Message; -import android.support.annotation.RequiresApi; import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; @@ -32,11 +31,13 @@ import android.webkit.WebStorage; import android.webkit.WebView; +import androidx.annotation.RequiresApi; + import java.lang.reflect.Method; /** - * @author cenxiaozhong * @update WebChromeClientWrapper rename to WebChromeClientDelegate + * @author cenxiaozhong * @since 1.0.0 */ public class WebChromeClientDelegate extends WebChromeClient { @@ -61,7 +62,6 @@ public void onProgressChanged(WebView view, int newProgress) { this.mDelegate.onProgressChanged(view, newProgress); return; } - } @Override @@ -80,7 +80,6 @@ public void onReceivedIcon(WebView view, Bitmap icon) { return; } super.onReceivedIcon(view, icon); - } @Override @@ -243,7 +242,6 @@ public void onGeolocationPermissionsHidePrompt() { @Override @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void onPermissionRequest(PermissionRequest request) { -// request.deny(); if (this.mDelegate != null) { this.mDelegate.onPermissionRequest(request); return; @@ -327,17 +325,29 @@ public boolean onShowFileChooser(WebView webView, ValueCallback filePathC } - // Android >= 4.1 + /** + * Android >= 4.1 + * @param uploadFile + * @param acceptType + * @param capture + */ public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) { commonRefect(this.mDelegate, "openFileChooser", new Object[]{uploadFile, acceptType, capture}, ValueCallback.class, String.class, String.class); } - // Android < 3.0 + /** + * Android < 3.0 + * @param valueCallback + */ public void openFileChooser(ValueCallback valueCallback) { commonRefect(this.mDelegate, "openFileChooser", new Object[]{valueCallback}, ValueCallback.class); } - // Android >= 3.0 + /** + * Android >= 3.0 + * @param valueCallback + * @param acceptType + */ public void openFileChooser(ValueCallback valueCallback, String acceptType) { commonRefect(this.mDelegate, "openFileChooser", new Object[]{valueCallback, acceptType}, ValueCallback.class, String.class); } @@ -356,8 +366,5 @@ private void commonRefect(WebChromeClient o, String mothed, Object[] os, Class.. ignore.printStackTrace(); } } - } - - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebCreator.java b/agentweb-core/src/main/java/com/just/agentweb/WebCreator.java index 0f79d44..ec9a69d 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebCreator.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebCreator.java @@ -18,7 +18,6 @@ import android.webkit.WebView; import android.widget.FrameLayout; - /** * @author cenxiaozhong * @since 1.0.0 diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebIndicator.java b/agentweb-core/src/main/java/com/just/agentweb/WebIndicator.java index d944512..4bcb3b4 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebIndicator.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebIndicator.java @@ -25,18 +25,18 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; +import androidx.annotation.Nullable; + /** * @author cenxiaozhong * @since 1.0.0 */ public class WebIndicator extends BaseIndicatorView implements BaseIndicatorSpec { - /** * 进度条颜色 */ @@ -53,7 +53,6 @@ public class WebIndicator extends BaseIndicatorView implements BaseIndicatorSpec * 控件的宽度 */ private int mTargetWidth = 0; - /** * 默认匀速动画最大的时长 */ @@ -83,11 +82,8 @@ public class WebIndicator extends BaseIndicatorView implements BaseIndicatorSpec public static final int UN_START = 0; public static final int STARTED = 1; public static final int FINISH = 2; - private float mTarget = 0f; - private float mCurrentProgress = 0F; - /** * 默认的高度 */ @@ -103,23 +99,18 @@ public WebIndicator(Context context, @Nullable AttributeSet attrs) { public WebIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mPaint = new Paint(); mColor = Color.parseColor("#1aad19"); mPaint.setAntiAlias(true); mPaint.setColor(mColor); mPaint.setDither(true); mPaint.setStrokeCap(Paint.Cap.SQUARE); - mTargetWidth = context.getResources().getDisplayMetrics().widthPixels; WEB_INDICATOR_DEFAULT_HEIGHT = AgentWebUtils.dp2px(context, 3); - } public void setColor(int color) { @@ -133,10 +124,8 @@ public void setColor(String color) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int wMode = MeasureSpec.getMode(widthMeasureSpec); int w = MeasureSpec.getSize(widthMeasureSpec); - int hMode = MeasureSpec.getMode(heightMeasureSpec); int h = MeasureSpec.getSize(heightMeasureSpec); @@ -147,29 +136,24 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { h = WEB_INDICATOR_DEFAULT_HEIGHT; } this.setMeasuredDimension(w, h); - } @Override protected void onDraw(Canvas canvas) { - } @Override protected void dispatchDraw(Canvas canvas) { canvas.drawRect(0, 0, mCurrentProgress / 100 * Float.valueOf(this.getWidth()), this.getHeight(), mPaint); } - @Override public void show() { - if (getVisibility() == View.GONE) { this.setVisibility(View.VISIBLE); mCurrentProgress = 0f; startAnim(false); } - } @Override @@ -185,9 +169,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { float rate = this.mTargetWidth / Float.valueOf(screenWidth); CURRENT_MAX_UNIFORM_SPEED_DURATION = (int) (MAX_UNIFORM_SPEED_DURATION * rate); CURRENT_MAX_DECELERATE_SPEED_DURATION = (int) (MAX_DECELERATE_SPEED_DURATION * rate); - } - LogUtils.i("WebProgress", "CURRENT_MAX_UNIFORM_SPEED_DURATION" + CURRENT_MAX_UNIFORM_SPEED_DURATION); } @@ -195,30 +177,24 @@ public void setProgress(float progress) { if (getVisibility() == View.GONE) { setVisibility(View.VISIBLE); } - if (progress < 95f) { + if (progress < 95f){ return; } if (TAG != FINISH) { startAnim(true); } } - @Override public void hide() { TAG = FINISH; } private void startAnim(boolean isFinished) { - - float v = isFinished ? 100 : 95; - - if (mAnimator != null && mAnimator.isStarted()) { mAnimator.cancel(); } mCurrentProgress = mCurrentProgress == 0f ? 0.00000001f : mCurrentProgress; - LogUtils.i("WebIndicator", "mCurrentProgress:" + mCurrentProgress + " v:" + v + " :" + (1f - mCurrentProgress)); if (!isFinished) { ValueAnimator mAnimator = ValueAnimator.ofFloat(mCurrentProgress, v); @@ -229,7 +205,6 @@ private void startAnim(boolean isFinished) { mAnimator.start(); this.mAnimator = mAnimator; } else { - ValueAnimator segment95Animator = null; if (mCurrentProgress < 95f) { segment95Animator = ValueAnimator.ofFloat(mCurrentProgress, 95); @@ -239,17 +214,13 @@ private void startAnim(boolean isFinished) { segment95Animator.setInterpolator(new DecelerateInterpolator()); segment95Animator.addUpdateListener(mAnimatorUpdateListener); } - - ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f); mObjectAnimator.setDuration(DO_END_ANIMATION_DURATION); ValueAnimator mValueAnimatorEnd = ValueAnimator.ofFloat(95f, 100f); mValueAnimatorEnd.setDuration(DO_END_ANIMATION_DURATION); mValueAnimatorEnd.addUpdateListener(mAnimatorUpdateListener); - AnimatorSet mAnimatorSet = new AnimatorSet(); mAnimatorSet.playTogether(mObjectAnimator, mValueAnimatorEnd); - if (segment95Animator != null) { AnimatorSet mAnimatorSet1 = new AnimatorSet(); mAnimatorSet1.play(mAnimatorSet).after(segment95Animator); @@ -259,10 +230,8 @@ private void startAnim(boolean isFinished) { mAnimatorSet.start(); mAnimator = mAnimatorSet; } - TAG = STARTED; mTarget = v; - } private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @@ -271,7 +240,6 @@ public void onAnimationUpdate(ValueAnimator animation) { float t = (float) animation.getAnimatedValue(); WebIndicator.this.mCurrentProgress = t; WebIndicator.this.invalidate(); - } }; @@ -285,7 +253,6 @@ public void onAnimationEnd(Animator animation) { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - /** * animator cause leak , if not cancel; */ @@ -307,7 +274,7 @@ private void doEnd() { @Override public void reset() { mCurrentProgress = 0; - if (mAnimator != null && mAnimator.isStarted()) { + if (mAnimator != null && mAnimator.isStarted()){ mAnimator.cancel(); } } @@ -315,7 +282,6 @@ public void reset() { @Override public void setProgress(int newProgress) { setProgress(Float.valueOf(newProgress)); - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebLifeCycle.java b/agentweb-core/src/main/java/com/just/agentweb/WebLifeCycle.java index a20cdff..32c194b 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebLifeCycle.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebLifeCycle.java @@ -22,13 +22,7 @@ * @since 1.0.0 */ public interface WebLifeCycle { - - void onResume(); - void onPause(); - void onDestroy(); - - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebListenerManager.java b/agentweb-core/src/main/java/com/just/agentweb/WebListenerManager.java index b55276c..24447dc 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebListenerManager.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebListenerManager.java @@ -27,13 +27,7 @@ * @since 1.0.0 */ public interface WebListenerManager { - - WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient); - WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient); - WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener); - - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebParentLayout.java b/agentweb-core/src/main/java/com/just/agentweb/WebParentLayout.java index cedcc19..93b18c0 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebParentLayout.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebParentLayout.java @@ -19,10 +19,6 @@ import android.app.Activity; import android.content.Context; import android.graphics.Color; -import android.support.annotation.IdRes; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -31,156 +27,152 @@ import android.webkit.WebView; import android.widget.FrameLayout; +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * @author cenxiaozhong * @date 2017/12/8 * @since 3.0.0 */ public class WebParentLayout extends FrameLayout implements Provider { - private AbsAgentWebUIController mAgentWebUIController = null; - private static final String TAG = WebParentLayout.class.getSimpleName(); - @LayoutRes - private int mErrorLayoutRes; - @IdRes - private int mClickId = -1; - private View mErrorView; - private WebView mWebView; - private FrameLayout mErrorLayout = null; - - WebParentLayout(@NonNull Context context) { - this(context, null); - LogUtils.i(TAG, "WebParentLayout"); - } - - WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, -1); - } - - WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - if (!(context instanceof Activity)) { - throw new IllegalArgumentException("WebParentLayout context must be activity or activity sub class ."); - } - this.mErrorLayoutRes = R.layout.agentweb_error_page; - } - - void bindController(AbsAgentWebUIController agentWebUIController) { - this.mAgentWebUIController = agentWebUIController; - this.mAgentWebUIController.bindWebParent(this, (Activity) getContext()); - } - - void showPageMainFrameError() { - - View container = this.mErrorLayout; - if (container != null) { - container.setVisibility(View.VISIBLE); - } else { - createErrorLayout(); - container = this.mErrorLayout; - } - View clickView = null; - if (mClickId != -1 && (clickView = container.findViewById(mClickId)) != null) { - clickView.setClickable(true); - } else { - container.setClickable(true); - } - } - - private void createErrorLayout() { - - final FrameLayout mFrameLayout = new FrameLayout(getContext()); - mFrameLayout.setBackgroundColor(Color.WHITE); - mFrameLayout.setId(R.id.mainframe_error_container_id); - if (this.mErrorView == null) { - LayoutInflater mLayoutInflater = LayoutInflater.from(getContext()); - LogUtils.i(TAG, "mErrorLayoutRes:" + mErrorLayoutRes); - mLayoutInflater.inflate(mErrorLayoutRes, mFrameLayout, true); - } else { - mFrameLayout.addView(mErrorView); - } - - ViewStub mViewStub = this.findViewById(R.id.mainframe_error_viewsub_id); - final int index = this.indexOfChild(mViewStub); - this.removeViewInLayout(mViewStub); - final ViewGroup.LayoutParams layoutParams = getLayoutParams(); - if (layoutParams != null) { - this.addView(this.mErrorLayout = mFrameLayout, index, layoutParams); - } else { - this.addView(this.mErrorLayout = mFrameLayout, index); - } - - mFrameLayout.setVisibility(View.VISIBLE); - if (mClickId != -1) { - final View clickView = mFrameLayout.findViewById(mClickId); - if (clickView != null) { - clickView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (getWebView() != null) { - clickView.setClickable(false); - getWebView().reload(); - } - } - }); - return; - } else { - - if (LogUtils.isDebug()) { - LogUtils.e(TAG, "ClickView is null , cannot bind accurate view to refresh or reload ."); - } - } - - } - - mFrameLayout.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (getWebView() != null) { - mFrameLayout.setClickable(false); - getWebView().reload(); - } - - } - }); - } - - void hideErrorLayout() { - View mView = null; - if ((mView = this.findViewById(R.id.mainframe_error_container_id)) != null) { - mView.setVisibility(View.GONE); - } - } - - void setErrorView(@NonNull View errorView) { - this.mErrorView = errorView; - } - - void setErrorLayoutRes(@LayoutRes int resLayout, @IdRes int id) { - this.mClickId = id; - if (this.mClickId <= 0) { - this.mClickId = -1; - } - this.mErrorLayoutRes = resLayout; - if (this.mErrorLayoutRes <= 0) { - this.mErrorLayoutRes = R.layout.agentweb_error_page; - } - } - - @Override - public AbsAgentWebUIController provide() { - return this.mAgentWebUIController; - } - - - void bindWebView(WebView view) { - if (this.mWebView == null) { - this.mWebView = view; - } - } - - WebView getWebView() { - return this.mWebView; - } - + private AbsAgentWebUIController mAgentWebUIController = null; + private static final String TAG = WebParentLayout.class.getSimpleName(); + @LayoutRes + private int mErrorLayoutRes; + @IdRes + private int mClickId = -1; + private View mErrorView; + private WebView mWebView; + private FrameLayout mErrorLayout = null; + + WebParentLayout(@NonNull Context context) { + this(context, null); + LogUtils.i(TAG, "WebParentLayout"); + } + + WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, -1); + } + + WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (!(context instanceof Activity)) { + throw new IllegalArgumentException("WebParentLayout context must be activity or activity sub class ."); + } + this.mErrorLayoutRes = R.layout.agentweb_error_page; + } + + void bindController(AbsAgentWebUIController agentWebUIController) { + this.mAgentWebUIController = agentWebUIController; + this.mAgentWebUIController.bindWebParent(this, (Activity) getContext()); + } + + void showPageMainFrameError() { + View container = this.mErrorLayout; + if (container != null) { + container.setVisibility(View.VISIBLE); + } else { + createErrorLayout(); + container = this.mErrorLayout; + } + View clickView = null; + if (mClickId != -1 && (clickView = container.findViewById(mClickId)) != null) { + clickView.setClickable(true); + } else { + container.setClickable(true); + } + } + + private void createErrorLayout() { + final FrameLayout mFrameLayout = new FrameLayout(getContext()); + mFrameLayout.setBackgroundColor(Color.WHITE); + mFrameLayout.setId(R.id.mainframe_error_container_id); + if (this.mErrorView == null) { + LayoutInflater mLayoutInflater = LayoutInflater.from(getContext()); + LogUtils.i(TAG, "mErrorLayoutRes:" + mErrorLayoutRes); + mLayoutInflater.inflate(mErrorLayoutRes, mFrameLayout, true); + } else { + mFrameLayout.addView(mErrorView); + } + ViewStub mViewStub = (ViewStub) this.findViewById(R.id.mainframe_error_viewsub_id); + final int index = this.indexOfChild(mViewStub); + this.removeViewInLayout(mViewStub); + final ViewGroup.LayoutParams layoutParams = getLayoutParams(); + if (layoutParams != null) { + this.addView(this.mErrorLayout = mFrameLayout, index, layoutParams); + } else { + this.addView(this.mErrorLayout = mFrameLayout, index); + } + mFrameLayout.setVisibility(View.VISIBLE); + if (mClickId != -1) { + final View clickView = mFrameLayout.findViewById(mClickId); + if (clickView != null) { + clickView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (getWebView() != null) { + clickView.setClickable(false); + getWebView().reload(); + } + } + }); + return; + } else { + if (LogUtils.isDebug()) { + LogUtils.e(TAG, "ClickView is null , cannot bind accurate view to refresh or reload ."); + } + } + } + mFrameLayout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (getWebView() != null) { + mFrameLayout.setClickable(false); + getWebView().reload(); + } + } + }); + } + + void hideErrorLayout() { + View mView = null; + if ((mView = this.findViewById(R.id.mainframe_error_container_id)) != null) { + mView.setVisibility(View.GONE); + } + } + + void setErrorView(@NonNull View errorView) { + this.mErrorView = errorView; + } + + void setErrorLayoutRes(@LayoutRes int resLayout, @IdRes int id) { + this.mClickId = id; + if (this.mClickId <= 0) { + this.mClickId = -1; + } + this.mErrorLayoutRes = resLayout; + if (this.mErrorLayoutRes <= 0) { + this.mErrorLayoutRes = R.layout.agentweb_error_page; + } + } + + @Override + public AbsAgentWebUIController provide() { + return this.mAgentWebUIController; + } + + + void bindWebView(WebView view) { + if (this.mWebView == null) { + this.mWebView = view; + } + } + + WebView getWebView() { + return this.mWebView; + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityCheckLogic.java b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityCheckLogic.java index 2b57e31..8ab0e60 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityCheckLogic.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityCheckLogic.java @@ -16,16 +16,15 @@ package com.just.agentweb; -import android.support.v4.util.ArrayMap; import android.webkit.WebView; +import androidx.collection.ArrayMap; + /** * @author cenxiaozhong */ public interface WebSecurityCheckLogic { void dealHoneyComb(WebView view); - void dealJsInterface(ArrayMap objects, AgentWeb.SecurityType securityType); - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityController.java b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityController.java index 2ed2b8c..2eb22bd 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityController.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityController.java @@ -20,7 +20,5 @@ * @author cenxiaozhong */ public interface WebSecurityController { - void check(T t); - } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityControllerImpl.java b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityControllerImpl.java index 4e349c0..11074c0 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityControllerImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityControllerImpl.java @@ -17,35 +17,33 @@ package com.just.agentweb; import android.os.Build; -import android.support.v4.util.ArrayMap; import android.webkit.WebView; +import androidx.collection.ArrayMap; + /** * @author cenxiaozhong */ public class WebSecurityControllerImpl implements WebSecurityController { - private WebView mWebView; - private ArrayMap mMap; - private AgentWeb.SecurityType mSecurityType; - - public WebSecurityControllerImpl(WebView view, ArrayMap map, AgentWeb.SecurityType securityType) { - this.mWebView = view; - this.mMap = map; - this.mSecurityType = securityType; - } - - @Override - public void check(WebSecurityCheckLogic webSecurityCheckLogic) { - - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { - webSecurityCheckLogic.dealHoneyComb(mWebView); - } - - if (mMap != null && mSecurityType == AgentWeb.SecurityType.STRICT_CHECK && !mMap.isEmpty()) { - webSecurityCheckLogic.dealJsInterface(mMap, mSecurityType); - } - - } + private WebView mWebView; + private ArrayMap mMap; + private AgentWeb.SecurityType mSecurityType; + + public WebSecurityControllerImpl(WebView view, ArrayMap map, AgentWeb.SecurityType securityType) { + this.mWebView = view; + this.mMap = map; + this.mSecurityType = securityType; + } + + @Override + public void check(WebSecurityCheckLogic webSecurityCheckLogic) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + webSecurityCheckLogic.dealHoneyComb(mWebView); + } + if (mMap != null && mSecurityType == AgentWeb.SecurityType.STRICT_CHECK && !mMap.isEmpty()) { + webSecurityCheckLogic.dealJsInterface(mMap, mSecurityType); + } + } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityLogicImpl.java b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityLogicImpl.java index 15475ab..4310e1a 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebSecurityLogicImpl.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebSecurityLogicImpl.java @@ -18,27 +18,26 @@ import android.annotation.TargetApi; import android.os.Build; -import android.support.v4.util.ArrayMap; import android.webkit.WebView; +import androidx.collection.ArrayMap; + /** * @author cenxiaozhong */ public class WebSecurityLogicImpl implements WebSecurityCheckLogic { - private String TAG = this.getClass().getSimpleName(); - + private String TAG=this.getClass().getSimpleName(); public static WebSecurityLogicImpl getInstance() { return new WebSecurityLogicImpl(); } - public WebSecurityLogicImpl() { - } + public WebSecurityLogicImpl(){} @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public void dealHoneyComb(WebView view) { - if (Build.VERSION_CODES.HONEYCOMB > Build.VERSION.SDK_INT || Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (Build.VERSION_CODES.HONEYCOMB > Build.VERSION.SDK_INT || Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1){ return; } view.removeJavascriptInterface("searchBoxJavaBridge_"); @@ -48,15 +47,13 @@ public void dealHoneyComb(WebView view) { @Override public void dealJsInterface(ArrayMap objects, AgentWeb.SecurityType securityType) { - - if (securityType == AgentWeb.SecurityType.STRICT_CHECK - && AgentWebConfig.WEBVIEW_TYPE != AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE - && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { - LogUtils.e(TAG, "Give up all inject objects"); + if (securityType== AgentWeb.SecurityType.STRICT_CHECK + &&AgentWebConfig.WEBVIEW_TYPE!=AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE + &&Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + LogUtils.e(TAG,"Give up all inject objects"); objects.clear(); objects = null; System.gc(); } - } } diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebViewClient.java b/agentweb-core/src/main/java/com/just/agentweb/WebViewClient.java new file mode 100644 index 0000000..02167ee --- /dev/null +++ b/agentweb-core/src/main/java/com/just/agentweb/WebViewClient.java @@ -0,0 +1,12 @@ +package com.just.agentweb; + +/** + * @author cenxiaozhong + * @date 2019/4/13 + * @since 1.0.0 + */ +public class WebViewClient extends MiddlewareWebClientBase { + public WebViewClient() { + } + +} diff --git a/agentweb-core/src/main/java/com/just/agentweb/WebViewClientDelegate.java b/agentweb-core/src/main/java/com/just/agentweb/WebViewClientDelegate.java index 1f9e0a9..aa59470 100644 --- a/agentweb-core/src/main/java/com/just/agentweb/WebViewClientDelegate.java +++ b/agentweb-core/src/main/java/com/just/agentweb/WebViewClientDelegate.java @@ -30,13 +30,12 @@ import android.webkit.WebViewClient; /** - * @author cenxiaozhong * @update WrapperWebViewClient rename WebViewClientDelegate + * @author cenxiaozhong * @date 2017/5/28 */ public class WebViewClientDelegate extends WebViewClient { - private WebViewClient mDelegate; private static final String TAG = WebViewClientDelegate.class.getSimpleName(); @@ -55,18 +54,14 @@ void setDelegate(WebViewClient delegate) { @Deprecated @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - - if (mDelegate != null) { return mDelegate.shouldOverrideUrlLoading(view, url); } - return super.shouldOverrideUrlLoading(view, url); } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - if (mDelegate != null) { return mDelegate.shouldOverrideUrlLoading(view, request); } @@ -75,7 +70,6 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - if (mDelegate != null) { mDelegate.onPageStarted(view, url, favicon); return; @@ -123,7 +117,6 @@ public WebResourceResponse shouldInterceptRequest(WebView view, @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - if (mDelegate != null) { return mDelegate.shouldInterceptRequest(view, request); } @@ -145,7 +138,6 @@ public void onTooManyRedirects(WebView view, Message cancelMsg, @Deprecated public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - if (mDelegate != null) { mDelegate.onReceivedError(view, errorCode, description, failingUrl); return; @@ -155,31 +147,26 @@ public void onReceivedError(WebView view, int errorCode, @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { - if (mDelegate != null) { mDelegate.onReceivedError(view, request, error); return; } - super.onReceivedError(view, request, error); } @Override public void onReceivedHttpError( WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { - if (mDelegate != null) { mDelegate.onReceivedHttpError(view, request, errorResponse); return; } super.onReceivedHttpError(view, request, errorResponse); - } @Override public void onFormResubmission(WebView view, Message dontResend, Message resend) { - if (mDelegate != null) { mDelegate.onFormResubmission(view, dontResend, resend); return; @@ -191,7 +178,6 @@ public void onFormResubmission(WebView view, Message dontResend, @Override public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { - if (mDelegate != null) { mDelegate.doUpdateVisitedHistory(view, url, isReload); return; @@ -232,15 +218,12 @@ public void onReceivedHttpAuthRequest(WebView view, public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { if (mDelegate != null) { return mDelegate.shouldOverrideKeyEvent(view, event); - } - return super.shouldOverrideKeyEvent(view, event); } @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - if (mDelegate != null) { mDelegate.onUnhandledKeyEvent(view, event); return; @@ -251,7 +234,6 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) { @Override public void onScaleChanged(WebView view, float oldScale, float newScale) { - if (mDelegate != null) { mDelegate.onScaleChanged(view, oldScale, newScale); return; @@ -262,7 +244,6 @@ public void onScaleChanged(WebView view, float oldScale, float newScale) { @Override public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { - if (mDelegate != null) { mDelegate.onReceivedLoginRequest(view, realm, account, args); return; diff --git a/agentweb-core/src/main/res/drawable/scrollbar_light.xml b/agentweb-core/src/main/res/drawable/scrollbar_light.xml deleted file mode 100644 index 8fe58b4..0000000 --- a/agentweb-core/src/main/res/drawable/scrollbar_light.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/agentweb-core/src/main/res/layout/agentweb_error_page.xml b/agentweb-core/src/main/res/layout/agentweb_error_page.xml index b3d8621..3924fba 100644 --- a/agentweb-core/src/main/res/layout/agentweb_error_page.xml +++ b/agentweb-core/src/main/res/layout/agentweb_error_page.xml @@ -1,16 +1,19 @@ - + android:background="@color/white" + > + android:textColor="#696969" + /> diff --git a/agentweb-core/src/main/res/values-zh/strings.xml b/agentweb-core/src/main/res/values-zh/strings.xml index 9dd3490..c9fc11d 100644 --- a/agentweb-core/src/main/res/values-zh/strings.xml +++ b/agentweb-core/src/main/res/values-zh/strings.xml @@ -6,6 +6,7 @@ 取消 下载失败! 当前进度:%s + 已下载:%s 您有一条新通知 文件下载 点击打开 @@ -16,4 +17,5 @@ 您需要离开%s前往其他应用吗? 离开 选择的文件不能大于%sMB + 出错啦! 点击空白处刷新 ~ diff --git a/agentweb-core/src/main/res/values/strings.xml b/agentweb-core/src/main/res/values/strings.xml index df570d0..08b0f00 100644 --- a/agentweb-core/src/main/res/values/strings.xml +++ b/agentweb-core/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Cancel Download failed! Downloading:%s + downloaded:%s You have a new notice Download Tap to continue @@ -16,4 +17,5 @@ leaving %s and opening another app? Go away The selected file can not be larger than %s MB + error~ diff --git a/agentweb-core/src/main/res/values/style.xml b/agentweb-core/src/main/res/values/style.xml index 6a687ec..8a53337 100644 --- a/agentweb-core/src/main/res/values/style.xml +++ b/agentweb-core/src/main/res/values/style.xml @@ -3,8 +3,7 @@ \ No newline at end of file diff --git a/agentweb-core/src/main/res/xml/web_files_paths.xml b/agentweb-core/src/main/res/xml/web_files_paths.xml deleted file mode 100644 index fb4626f..0000000 --- a/agentweb-core/src/main/res/xml/web_files_paths.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore index b245ef2..bc61d10 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,4 @@ -/build -/src/main/java/me/wizos/loread/protocol/ -/src/main/java/me/wizos/loread/list/ +/build/ +/src/androidTest/ +/src/test/ +/release/ diff --git a/app/build.gradle b/app/build.gradle index 199328e..7e9681b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,136 +1,179 @@ apply plugin: 'com.android.application' -apply plugin: 'org.greenrobot.greendao' android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.3' defaultConfig { - applicationId "me.wizos.loread" minSdkVersion 22 - targetSdkVersion 27 - versionCode 6 - versionName "1.2.1" + targetSdkVersion 29 + applicationId "me.wizos.loread" + versionCode 1 + versionName "1.0.0" ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' } + // 解决 Error:Execution failed for task ‘:app:javaPreCompileDebug 问题 + javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } } } buildTypes { release { minifyEnabled false //启动混淆 - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // proguardFile是混淆使用的配置文件,这里是module根目录下的proguard-rules.pro文件 - manifestPlaceholders = [ - //AAI4F2S2LM1U 属于应用"知微"独有的 Android AppKey, 用于配置SDK - MTA_APPKEY : "AAI4F2S2LM1U", - //标注应用推广渠道用以区分新用户来源,可填写如应用宝,豌豆荚等 - MTA_CHANNEL: "酷安" - ] + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + // AAI4F2S2LM1U 属于应用"知微"独有的 Android AppKey, 用于配置SDK;标注应用推广渠道用以区分新用户来源,可填写如应用宝,豌豆荚等 + manifestPlaceholders = [MTA_APPKEY : "AAI4F2S2LM1U", MTA_CHANNEL: "酷安"] } debug { - minifyEnabled false //不启动混淆 - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - // proguardFile是混淆使用的配置文件,这里是module根目录下的proguard-rules.pro文件 - manifestPlaceholders = [ - //AAI4F2S2LM1U 属于应用"知微"独有的 Android AppKey, 用于配置SDK - MTA_APPKEY : "AAI4F2S2LM1U", - //标注应用推广渠道用以区分新用户来源,可填写如应用宝,豌豆荚等 - MTA_CHANNEL: "酷安" - ] + //applicationIdSuffix '.baimu' //增加包名后缀 + minifyEnabled false + debuggable true + manifestPlaceholders = [MTA_APPKEY : "AAI4F2S2LM1U", MTA_CHANNEL: "酷安"] } } - sourceSets { - main { - java.srcDirs = ['src/main/java', 'src/main/java-gen'] - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } } -// --offline --refresh-dependencies dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support.constraint:constraint-layout:1.1.2' - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support:support-v4:27.1.1' - implementation 'com.android.support:design:27.1.1' - implementation 'com.android.support:cardview-v7:27.1.1' -// implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-14' + // 开发工具 + testImplementation 'junit:junit:4.13' + implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.1.0' + implementation "androidx.paging:paging-runtime:2.1.2" + implementation "androidx.work:work-runtime:2.3.4" + // 通用的背景设置库,通过标签直接生成shape,无需再写shape.xml [https://github.com/JavaNoober/BackgroundLibrary] + implementation 'com.noober.background:core:1.6.3' // 列表项左右滑动布局 implementation project(path: ':swipelayout') - // 基于 Android WebView 一个功能完善小型浏览器库(由于目前只有loadUrl方法,无法直接加载内容数据,不好用) + implementation project(path: ':support') + // 加载 view [https://github.com/ybq/Android-SpinKit] + // implementation 'com.github.ybq:Android-SpinKit:1.4.0' + // 浏览器 implementation project(path: ':agentweb-core') - // compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.4' - // 在后面加上@aar,意指你只是下载该aar包,而并不下载该aar包所依赖的其他库,那如果想在使用@aar的前提下还能下载其依赖库,则需要添加transitive=true的条件。 - implementation('com.github.afollestad.material-dialogs:core:0.8.5.8@aar') { - transitive = true - } + + // https://github.com/drakeet/MultiType, 'com.github.kelinZhou:MultiTypeAdapter:1.0.2' + implementation 'com.drakeet.multitype:multitype:4.2.0' + + // 对话框 + // implementation 'com.kongzue.dialog_v3x:dialog:3.1.6' + // implementation 'com.orhanobut:dialogplus:1.11@aar' + // implementation 'com.afollestad.material-dialogs:core:3.1.1' + implementation 'com.afollestad.material-dialogs:core:0.9.6.0' + implementation 'com.afollestad.material-dialogs:commons:0.9.6.0' + // 透明通知栏 - implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3' - implementation 'org.greenrobot:eventbus:3.1.1' + // implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3' + + // popup [https://github.com/li-xiaojun/XPopup] + implementation 'com.lxj:xpopup:1.8.13' + // toast [https://github.com/getActivity/ToastUtils] + implementation 'com.hjq:toast:8.0' + // 开关按钮 - implementation 'com.kyleduo.switchbutton:library:1.4.0' - implementation 'com.xw.repo:xedittext:2.0.5@aar' - implementation 'me.zhanghai.android.materialedittext:library:1.0.2' - - // ORM框架数据库,升级指南:http://www.cnblogs.com/dsxniubility/p/5699629.html - greendao { - schemaVersion 9 - targetGenDir 'src/main/java' //生成代码放的路径 - daoPackage 'me.wizos.loread.db.dao' - } - implementation 'org.greenrobot:greendao:3.2.2' - implementation 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.0.2' + implementation 'com.kyleduo.switchbutton:library:2.0.0' + + // 让播放、暂停按钮优雅的过渡 [https://github.com/Lauzy/PlayPauseView] + implementation 'com.github.Lauzy:PlayPauseView:1.0.7' + + // 带删除功能的EditText;显示或者隐藏密码;可设置自动添加分隔符分割电话号码、银行卡号等;支持禁止Emoji表情符号输入 [https://github.com/woxingxiao/XEditText] + // 用在了 activity_search.xml 中 + implementation 'com.xw.repo:xedittext-androidx:2.2.6@aar' + // [https://github.com/zhanghai/materialedittext] + // 用在了 activity_login_tiny_tiny_rss_rss.xml 中 + implementation 'me.zhanghai.android.materialedittext:library:1.0.5' + + // 时间总线 + // implementation 'org.greenrobot:eventbus:3.1.1' + implementation 'com.jeremyliao:live-event-bus-x:1.6.1' + + implementation "androidx.room:room-runtime:2.2.5" + annotationProcessor "androidx.room:room-compiler:2.2.5" + // implementation 'com.tencent.wcdb:room:1.0.8' // 代替 room-runtime,同时也不需要再引用 wcdb-android + // annotationProcessor 'android.arch.persistence.room:compiler:1.1.1' // compiler 需要用 room 的 + + // 基于 mmap 的高性能通用 key-value 组件 + // implementation 'com.tencent:mmkv:1.1.0' - // 【开发工具】 - testImplementation 'junit:junit:4.12' // 内存泄漏检测工具 - debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1' + // debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1' + // releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1' + // 日志工具 -// implementation files('libs/klog.jar') + implementation 'com.orhanobut:logger:2.2.0' implementation 'com.github.zhaokaiqiang.klog:library:1.6.0' - //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如:2.3.1 -// implementation 'com.tencent.bugly:crashreport:2.6.6.1' - implementation files('libs/bugly_crashreport_upgrade-1.3.5.aar') - //腾讯统计MTA主包 - implementation 'com.qq.mta:mta:3.4.2' - //腾讯统计MID基础包 - implementation 'com.tencent.mid:mid:3.73-release' + + // 权限申请 [https://github.com/getActivity/XXPermissions] + implementation 'com.hjq:xxpermissions:6.2' + + // 其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如:2.6.6.1 + implementation 'com.tencent.bugly:crashreport:3.1.0' + // 腾讯统计MTA主包 + implementation 'com.qq.mta:mta:3.4.7-release' + // 腾讯统计MID基础包 + implementation 'com.tencent.mid:mid:4.06-Release' // 依赖注入 - annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' - implementation 'com.jakewharton:butterknife:8.8.1' + implementation 'com.jakewharton:butterknife:10.2.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1' + + // 序列化数据 [https://github.com/google/gson] + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'org.parceler:parceler-api:1.1.13' + annotationProcessor 'org.parceler:parceler:1.1.13' - // 序列化数据 - implementation 'com.google.code.gson:gson:2.8.2' - implementation 'org.parceler:parceler-api:1.1.5' + // 抽取的正文没有html标签 + // implementation 'com.chimbori.crux:crux:2.2.0' + // 正文抽取器 + // implementation project(path: ':extractor') + + // HTML 解析 [https://github.com/jhy/jsoup] + implementation 'org.jsoup:jsoup:1.13.1' // HTML 解析 - implementation 'org.jsoup:jsoup:1.11.3' - // 图片加载 - implementation 'com.github.bumptech.glide:glide:4.6.1' - annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' - implementation 'com.github.chrisbanes:PhotoView:2.1.3' - // okHttp - implementation files('libs/okhttp-3.9.0.jar') - implementation files('libs/okio-1.13.0.jar') - implementation files('libs/okgo-3.0.4.jar') + // implementation ('com.virjar:sipsoup:1.6'){ transitive = false } + // 使用xpath解析提取html数据 [https://github.com/zhegexiaohuozi/JsoupXpath] + // implementation 'cn.wanghaomiao:JsoupXpath:2.3.2' + // JSON 选择库 [https://github.com/json-path/JsonPath] + // implementation 'com.jayway.jsonpath:json-path:2.4.0' + // JS 库 [https://github.com/APISENSE/rhino-android] + implementation 'io.apisense:rhino-android:1.1.1' - implementation project(path: ':luban') + // [https://github.com/bumptech/glide] + implementation 'com.github.bumptech.glide:glide:4.11.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' + // Glide 底层使用okhttp3 + implementation('com.github.bumptech.glide:okhttp3-integration:4.11.0') { transitive = false } + // 查看大图 [https://github.com/SherlockGougou/BigImageViewPager] + implementation 'com.github.SherlockGougou:BigImageViewPager:androidx-6.0.1' - // 基于readability的改进版,https://github.com/chimbori/crux -// implementation project(path: ':crux') -// implementation 'net.dankito.readability4j:readability4j:1.0' + // 音频部件 [https://github.com/yhaolpz/FloatWindow] + implementation project(path: ':floatwindow') + // https://github.com/SDKers/FloatWindow, https://github.com/fenggit/FloatWindow - // Android-Job 在运行判断使用哪种API,它提供 AlarmManager, JobScheduler和 GcmNetworkManager功能的超集,比如说,我们可以定义计划任务在网络连通且在充电时候执行。 -// implementation 'com.evernote:android-job:1.2.6' + // retrofit2 + implementation('com.squareup.retrofit2:retrofit:2.8.1') { transitive = false } + implementation('com.squareup.retrofit2:converter-gson:2.8.1') { transitive = false } + // okHttp + implementation 'com.squareup.okhttp3:okhttp:4.4.1' + implementation 'com.squareup.okio:okio:2.5.0' + implementation files('libs/okgo-3.0.4.jar') + // 网络状态监听 [https://github.com/allenlzhang/NetworkState] + implementation 'com.github.allenlzhang:NetworkState:v1.0.0' + // 图片压缩 [https://github.com/Curzibn/Luban] + implementation project(path: ':luban') - // 掌阅的xml布局文件转代码,提升布局加载速度 -// annotationProcessor project(':apt') -// implementation project(':x2c') - // 搜索栏 -// compile 'com.miguelcatalan:materialsearchview:1.4.0' - // 在该项目中引用其他本地项目:http://blog.csdn.net/YellowStar5/article/details/53678044。注意,其他项目中有同名的资源id,将优先使用该项目的 -// compile 'org.sufficientlysecure:html-textview:1.4' + // 防止三方 SDK 中常见的损害用户体验的行为 [https://github.com/oasisfeng/condom] + implementation 'com.oasisfeng.condom:library:2.5.0' } diff --git a/app/libs/bugly_crashreport_upgrade-1.3.5.aar b/app/libs/bugly_crashreport_upgrade-1.3.5.aar deleted file mode 100644 index 09262ed..0000000 Binary files a/app/libs/bugly_crashreport_upgrade-1.3.5.aar and /dev/null differ diff --git a/app/libs/okhttp-3.9.0.jar b/app/libs/okhttp-3.9.0.jar deleted file mode 100644 index 58ad4be..0000000 Binary files a/app/libs/okhttp-3.9.0.jar and /dev/null differ diff --git a/app/libs/okio-1.13.0.jar b/app/libs/okio-1.13.0.jar deleted file mode 100644 index 02c302f..0000000 Binary files a/app/libs/okio-1.13.0.jar and /dev/null differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index bc6c219..7df26a6 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,4 +1,4 @@ -# Add project specific ProGuard rules here. +# Add project specific ProGuard replaceUrl here. # By default, the flags in this file are appended to flags specified # in C:\Program Files\Android\sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles @@ -10,7 +10,7 @@ # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface +# and specify the fully qualified class userName to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e813f3..053f834 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,169 +1,247 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + - + - + - - - - - - + + + + + + + - - - + tools:replace="android:networkSecurityConfig,android:appComponentFactory" + tools:targetApi="q"> + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + android:theme="@style/AppBaseTheme.SplashTranslucentTheme" /> + + + + + + + + + - - - - - - - - - - - - - - - + android:name=".activity.LabActivity" + android:configChanges="orientation|screenSize|keyboardHidden" + android:hardwareAccelerated="true" + android:launchMode="singleTop" /> + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - \ No newline at end of file diff --git a/app/src/main/assets/css/android_studio.css b/app/src/main/assets/css/android_studio.css new file mode 100644 index 0000000..bc8e473 --- /dev/null +++ b/app/src/main/assets/css/android_studio.css @@ -0,0 +1,66 @@ +/* +Date: 24 Fev 2015 +Author: Pedro Oliveira +*/ + +.hljs { + color: #a9b7c6; + background: #282b2e; + display: block; + overflow-x: auto; + padding: 0.5em; +} + +.hljs-number, +.hljs-literal, +.hljs-symbol, +.hljs-bullet { + color: #6897BB; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-deletion { + color: #cc7832; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-link { + color: #629755; +} + +.hljs-comment, +.hljs-quote { + color: #808080; +} + +.hljs-meta { + color: #bbb529; +} + +.hljs-string, +.hljs-attribute, +.hljs-addition { + color: #6A8759; +} + +.hljs-section, +.hljs-title, +.hljs-type { + color: #ffc66d; +} + +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e8bf6a; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/app/src/main/assets/css/article_theme_day.css b/app/src/main/assets/css/article_theme_day.css index 23da15f..5333f1e 100644 --- a/app/src/main/assets/css/article_theme_day.css +++ b/app/src/main/assets/css/article_theme_day.css @@ -2,22 +2,22 @@ background-color:rgba(0,0,0,0);*/ body { - color: #333; + //color: #333; +color:#293845; background-color: #FFFFFF; } /* 标题 =============================================================================*/ +/* h1,h2 { color: #000; -} -h2 { - border-bottom: 1px solid #ccc; + border-bottom: 1px dashed #ccc; } hr { - background-color:#f8f8f8; + //background-color:#f8f8f8; } - +*/ /* 表格 =============================================================================*/ @@ -28,19 +28,13 @@ table th, table td { border: 1px solid #ccc; } -/*单数行*/ -/*table tr { - background-color: #f8f8f8; -}*/ -/*双数行*/ -/*table tr:nth-child(2n){ - background-color: #fff; -}*/ - - /* 代码 =============================================================================*/ code, tt ,pre { border: 1px solid #eaeaea; background-color: #f8f8f8; +} +blockquote{ + background: rgba(0, 0, 0, 0.04) !important; + color: rgba(0, 0, 0, 0.5) !important; } \ No newline at end of file diff --git a/app/src/main/assets/css/article_theme_night.css b/app/src/main/assets/css/article_theme_night.css index 47232f7..92a08c6 100644 --- a/app/src/main/assets/css/article_theme_night.css +++ b/app/src/main/assets/css/article_theme_night.css @@ -1,56 +1,60 @@ -/* #203238 浅绿 - #141e21; 深绿 - background-color:rgba(0,0,0,0); -***/ - -body { - color: #97A0A5; - background-color: #202B2F; +::-webkit-scrollbar { + width: 8px; + height: 8px; + background: #2c3e50; } -/* 标题 -=============================================================================*/ -h1,h2 { - color: #E3E8E7; +::-webkit-scrollbar-thumb { + background: #888; } -h2 { - border-bottom: 1px solid #ccc; + +a { + color: #c6cddb; } -hr { - background-color: #141e21; + +body{ + color: #c6cddb; + //background: #494f5c; + background-color: #454952; } /* 表格 =============================================================================*/ -table{ - background-color: #203238; + /*background-color: #141e21; + border: 1px solid #203238;*/ +pre { + color: #eee; + background: #2c3e50; } -table th, table td { - border: 1px solid #212121; -} -/*单数行*/ -/*table tr { - background-color: #203238; -}*/ - -/*双数行*/ -/*table tr:nth-child(2n) { - background-color: #203238; -}*/ - -/* 代码 -=============================================================================*/ -code, tt , pre { +code { + color: #eee; + background: #7d828a; +} +table{ + color: #eee; + border: 1px solid rgba(0, 0, 0, 0.1) !important; + background: rgba(0, 0, 0, 0.08) !important; + /*background-color: #203238;*/ +} +tt { background-color: #141e21; border: 1px solid #203238; } +table th, table td { + //border: 1px solid #212121; + border: 1px solid rgba(0, 0, 0, 0.1); +} -/* 图片 -=============================================================================*/ -/* 降低图片亮度 */ +/* 降低亮度 */ img,video,iframe,embed { -webkit-filter:brightness(0.75); -} \ No newline at end of file +} +blockquote{ + background: rgba(0, 0, 0, 0.08) !important; + border-left: 5px solid rgba(0, 0, 0, 0.3) !important; + color: rgba(255, 255, 255, 0.6) !important; +} + diff --git a/app/src/main/assets/css/normalize.css b/app/src/main/assets/css/normalize.css index 657abe4..5e74bde 100644 --- a/app/src/main/assets/css/normalize.css +++ b/app/src/main/assets/css/normalize.css @@ -4,135 +4,138 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq padding: 0; border: 0; } +:focus{outline:none} -html { - max-width: 100%; - word-break: break-all; +@font-face { + font-family: 'roboto_light'; + src: url('file:///android_asset/fonts/roboto_light.ttf'); } -body,div,p { - font-family:Noto Sans,Helvetica Neue,PingFang SC,Microsoft YaHei,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif !important; - font-size: 16px !important; - line-height: 1.6 !important; +body { + padding: 20px 15px !important; + word-wrap: break-word !important; +} + +body,div,p,font { + font-family: roboto_light,sans-serif !important; + font-size: 16px; + line-height: 160% !important; + letter-spacing:1px; } body > *:first-child { margin-top: 0 !important; } - body > *:last-child { margin-bottom: 0 !important; } - -strong,b { - font-style: italic -} -/* 图片,视频 -=============================================================================*/ -/* 隐藏空图片 */ -img[src=''],iframe[src=''] { - display: none !important; -} - -img { +body * { max-width: 100% !important; - width: auto !important; - height: auto !important; - margin: 5px 0 !important; } -audio,video,iframe,embed,pre,canvas{ - max-width: 100% !important; - width: 100% !important; - height: auto !important; - display: inline-block !important; - margin: 5px 0 !important; +/* BLOCKS */ +p, ul, ol, dl, table, pre, figure { + margin: 10px 0; } - -video,iframe,embed { - background:repeating-linear-gradient(-50deg,#E8E8E8,#E8E8E8 1em,#d2d2d2 1em,#d2d2d2 2em) !important; +table pre{ + margin: 0px 0; } -/* HTML5 媒体文件跟 img 保持一致 */ -audio { - height: 32px; -} -input{ - width:auto !important; +/* + * 设置最大宽度,防止超出屏幕 + */ +div,p,tbody,textarea,ins,input{ max-width: 100% !important; } -/* 取消点击后的橙色框。此处只设置了input、a、div,也可继续添加其他标签。*/ -input:focus,a:focus,div:focus{ - outline:none; -} - -/* BLOCKS -=============================================================================*/ -p,section,code,pre,article { - word-wrap: break-word !important; /* 自动折行 */ - background: transparent !important; +/* + * 如果表格内的div宽度过长,设置最大宽度是无法防止溢出的,所以得手动将其宽度改为自适应 + */ +table div{ + width:auto !important; } -p, blockquote, ul, ol, dl, table, pre { - margin: 15px 0; -} -div,p { - max-width: 100% !important; +* { + background-image:none !important; } -/* HEADERS -=============================================================================*/ -h1, h2, h3, h4, h5, h6 { - margin: 10px 0 10px; - padding: 0; - font-weight: bold; - -webkit-font-smoothing: antialiased; +p{ + text-indent:0 !important; + background:none !important; + width:auto !important; + height:auto !important; } +/* HEADERS */ h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { font-size: inherit; } -h1 { - font-size: 22px; +section h1,h2,h3,h4,h5,h6{ + font-family: roboto_light !important; + padding: 0 0px !important; + margin: 15px 0px !important; } +h4:before{ + content: "⭕️"; +} +h5:before{ + content: "💢"; +} +h6:before{ + content: "🌀"; +} +section h1 { + font-size: 24px; + box-shadow: inset 0 -8px 0 #bf53b1; +} h2 { font-size: 20px; + box-shadow: inset 0 -6px 0 #f7615f; } - h3 { - font-size: 18px; + font-size: 16px; + box-shadow: inset 0 -2px 0 #e0af55; } - h4 { font-size: 16px; + color: #d2cd3e; } - -h5, h6 { - font-size: 14px; +h5 { + font-size: 16px; + color: #6AC749; +} +h6 { + font-size: 16px; + color: #32b9a8; } -/* LINKS -=============================================================================*/ +/* LINKS */ a { - color: #4183C4; - text-decoration:  underline !important; + color: #51a1f1 !important; } - -a:hover { - text-decoration: underline; +a > svg{ + width: 14px; + height:auto; +} +section a { + text-decoration: underline !important; +} +#title a { + text-decoration: none; +} +a[href=''],a:not([href]) { + text-decoration: none !important; } - -/* 列表 -=============================================================================*/ +/* 列表 */ ul, ol { - padding-left: 30px; + padding: 0 0 0 1em; + list-style-position:inside; } ul li > :first-child, @@ -143,21 +146,23 @@ ul li ol:first-of-type, ol li ul:first-of-type { margin-top: 0px; } - ul ul, ul ol, ol ol, ol ul { margin-bottom: 0; } - +li{ + margin: .2em 0; +} +/* dl { padding: 0; } - +*/ dl dt { font-size: 14px; font-weight: bold; font-style: italic; - padding: 0; margin: 15px 0 5px; + /*padding: 0;*/ } dl dt:first-child { @@ -185,147 +190,180 @@ dl dd > :last-child { margin-bottom: 0px; } -/* CODE -=============================================================================*/ -pre, code, tt { - font-size: 12px; - font-family: Consolas, "Liberation Mono", Courier, monospace; +/* 表格 */ +table { + width: auto !important; + height: auto !important; + margin: 0 !important; + border-collapse: collapse !important; + border-spacing: 0; + font-size: .8em; + line-height: 1.1; + max-width: 100% !important; + padding: .5em .5em; + overflow: auto; + word-wrap: break-word !important; + letter-spacing: normal; + border-radius: 5px; } - -code, tt { - margin: 0 0px; - padding: 0 0px; - border-radius: 3px; +table th { + font-weight: bold; +} +table th,table td { + padding: 5px !important; + width: auto !important; + max-width: 100% !important; + height: auto !important; +} +thead > tr, tbody > tr:nth-child(2n) { + background: rgba(0, 0, 0, 0.06) !important; +} +table p{ + margin: 5px 0; +} +/* 垂直居中 */ +th img, td img{ + vertical-align:middle; +} +/* 对齐是排版最重要的因素, 别让什么都居中 */ +caption, th { + text-align: left; } -pre > code { - margin: 0; - padding: 0; - white-space: pre; - border: none; - background: transparent; +td pre, td code,pre code, pre tt { + border: 0 !important; + background: transparent !important; } +/* CODE */ +pre code, pre, code, tt { + font-family: Consolas, Liberation Mono Courier, monospace; +} pre { - padding: 5px 0; - line-height: 19px; + font-size: .8em; + line-height: 1.5; + max-width: 100% !important; + padding: .5em .5em; overflow: auto; + word-wrap: break-word !important; + letter-spacing: normal; + white-space: pre; + border-radius: 5px; + text-align: left; +} +pre code,table pre{ + font-size: .8em; +} +code { + font-size: .8em; border-radius: 3px; - white-space: pre-wrap!important; - word-wrap: break-word!important; - *white-space: normal!important; + padding: 0 3px; + margin: 0 3px; + word-break: break-all; + letter-spacing:normal; } - -pre code, pre tt { - background-color: transparent; - border: none; +tt { + margin: 0 0px; + padding: 0 0px; } -/* QUOTES -=============================================================================*/ +/* QUOTES */ blockquote { - border-left: 4px solid #4183C4; - padding: 0 15px; - color: #777; + font-size: .8em !important; + padding: 5px 5px 5px 15px !important; + margin: 15px 0 15px 0px !important; } - blockquote > :first-child { - margin-top: 0px; + margin-top: 0px !important; } - blockquote > :last-child { - margin-bottom: 0px; + margin-bottom: 0px !important; } -/* 表格 -================================*/ -table { - table-layout: fixed !important; - border-collapse: collapse !important; - border-spacing: 0; - padding: 5px 0; - max-width: 100% !important; - height: auto !important; +/* figure */ +figure { + max-width: 100%; + height: auto; + text-align: center; } -td,object{ - max-width: 100% !important; - height: auto !important; +figcaption { + font-size: .8em; + font-style: italic; + opacity: .6; } -td > pre, td > code { - border: 0 !important; +textarea{ + display:none; } -table th { - font-weight: bold; +/* 图片,视频 */ +/* 隐藏"空/无效"元素 */ +img[src=''],audio:not([src]),iframe:not([src]),[src=''],blockquote:empty,figure:empty,table:empty,pre:empty,code:empty,div:empty { + display: none !important; } -table th, table td { - padding: 2px 0px !important; - font-size: 14px !important; +img { + margin: 5px 0 !important; + max-width: 100% !important; + width: auto; + height: auto !important; + border-radius: 5px; } -/* 对齐是排版最重要的因素, 别让什么都居中 */ -caption, th { - text-align: left; -} - - -/* 文章的排版 -================================*/ -hr { - margin: 8px 0 !important; - border: none !important; - border-bottom: 1px dashed #ddd !important; +a > img{ + display: block; } - -#hr { - margin: 10px 0px; - border-bottom: 1px solid #ddd !important; +video,iframe,embed { + width: 100% !important; + height: 100% !important; + display: block !important; } - -article { - padding: 10px 10px; +.video_wrap,.iframe_wrap,.embed_wrap { + margin: 5px 0 !important; + border-bottom: 5px solid rgba(0, 0, 0, 0.1) !important; + border-top: 5px solid rgba(0, 0, 0, 0.1) !important; + border-radius: 5px !important; + background:repeating-linear-gradient(-50deg,#E8E8E8,#E8E8E8 1em,#d2d2d2 1em,#d2d2d2 2em) !important; } - -#title { - text-align: center; - font-size: 20px; +.table_wrap{ + overflow-x:auto !important; } - -#title a { - text-decoration: none +audio { + height: 32px; + width: 100% !important; + margin: 5px 0 !important; +} +video { + background:rgba(0, 0, 0); } -#author { - text-align: center; - font-size: 14px; + +/* 文章相关信息的排版*/ +hr { + opacity: .2; + border-width: 0 0 5px; + border-style: dashed; + background: transparent; + width: 50%; + margin: .9em auto; +} +#title { + font-family:roboto_light,sans-serif !important; + font-size: 1.4em; margin-bottom: 5px; - opacity: 0.4; } - -#pubDate { - margin: 0px 0; - text-align: center; - font-size: 14px; +#author,#pubDate { + font-size: .8em !important; + margin: 0 !important; opacity: 0.4; } -#video-button { - background-color:#4183c4; - border: none; - color: white; - padding: 8px 0px; - font-size: 12px; - - position: absolute; - bottom: 0; - left: 0; - text-align: center; - height: 32px; - width: 100%; - cursor: pointer; - z-index: 6; -} #readability-button{ font-size:12px; margin-top: 10px } +#content{ + margin-top: 10px +} + +del{ + opacity:0.4; +} diff --git a/app/src/main/assets/fonts/roboto_light.ttf b/app/src/main/assets/fonts/roboto_light.ttf new file mode 100644 index 0000000..aa45340 Binary files /dev/null and b/app/src/main/assets/fonts/roboto_light.ttf differ diff --git a/app/src/main/assets/hosts.txt b/app/src/main/assets/hosts.txt deleted file mode 100644 index 19161ef..0000000 --- a/app/src/main/assets/hosts.txt +++ /dev/null @@ -1,2121 +0,0 @@ -0.r.msn.com -1100.adsina.allyes.com -1148.adsina.allyes.com -114.allyes.com -114so.cn -123.sogou.com -1251.adsina.allyes.com -1276.adsina.allyes.com -144.dragonparking.com -154.adsina.allyes.com -161.adsina.allyes.com -163.wrating.com -17173im.allyes.com -18av.mm-cg.co -1.allyes.com.cn -1.wps.cn -1.yhzm.cc -2016.sina.cn -2052.flash2-http.qq.com -312036.com -3dns-2.adobe.com -3dns-3.adobe.com -4171764.fls.doubleclick.net -44.adsina.allyes.com -45.adsina.allyes.com -46sg.com -58749.admaster.com.cn -58lm.vip -60.adsina.allyes.com -79t2.admaster.com.cn -801.tianya.cn -803.tianya.cn -809.ok365.com -817.dopa.com -818.dopa.com -818.ok365.com -8b4pea.c.admaster.com.cn -8b6dfa.v.admaster.com.cn -8chzga.v.admaster.com.cn -8dcyqu.c.admaster.com.cn -930.dragonparking.com -a1click.cpc.sogou.com -a1.img.static.youmi.net -a2.img.static.youmi.net -a3.allyes.com -a3.img.static.youmi.net -a-ad.adwo.com -a.ads1.msn.com -a.ads2.msn.com -a.alimama.cn -a.appjiagu.com -aax.amazon-adsystem.com -aax-cpm.amazon-adsystem.com -aax-eu.amazon-adsystem.com -aax-fe.amazon-adsystem.com -aax-fe-pek.amazon-adsystem.com -aax-fe-sin.amazon-adsystem.com -aax-us-east.amazon-adsystem.com -a.baidu.com -abc.xtyx918.com -a.beilamusi.com -a.benshiw.net -abtest.mistat.xiaomi.com -ac3.msn.com -ac.atpanel.com -acceptable.a-ads.com -acdn.adnxs.com -acjs.aliyun.com -ac.mmstat.com -acookie.alimama.com -acs4baichuan.m.taobao.com -act.commercial.shouji.360.cn -activate.adobe.com -activate-sea.adobe.com -activate-sjc0.adobe.com -activate.wip3.adobe.com -activeqq.3g.qq.com -activity.yuyiya.com -actsdk.idreamsky.com -acuityplatform.com -ad.1111cpc.com -ad1.greedland.net -ad1.sina.com.cn -ad1.udn.com -ad2.sina.com.cn -ad2.udn.com -ad2.yam.com -ad.363.in -ad3.sina.com.cn -ad3.udn.com -ad4.sina.com -ad4.sina.com.cn -ad4.udn.com -ad5.sina.com.cn -ad.adhouyi.cn -adadvisor.net -ad.adwo.com -ad-apac.doubleclick.net -adapi.lenovogame.com -ad.api.moji.com -adapi.shouji.360.cn -adasad.myweb.hinet.net -adashbc.m.taobao.com -adashbc.ut.taobao.com -adash.man.aliyuncs.com -adash.m.taobao.com -adashxgc.ut.taobao.com -adashx.m.taobao.com -ad-bg.adwo.com -ad.caiyunapp.com -adcast.deviantart.com -ad.cdn.sex -adclick.g.doubleclick.net -ad.cn.doubleclick.net -adcore.lenovomm.com -ad-count.adwo.com -ad-delivery.net -ad.dev.360.cn -ad.doubleclick.net -ad.dqwjzm.com -ad.duapps.com -adeventtracker.spotify.com -adfarm.mediaplex.com -adfill.adview.cn -adfilter.imtt.qq.com -ad.flurry.com -adfuture.cn -ad.gamebox.360.cn -adgeo.163.com -ad.greedland.net -adimages.sina.com.hk -adimg.daumcdn.net -adimg.deviantart.net -adimg.mobile.sina.cn -adimg.qxlsjw.com -adimg.uve.weibo.com -adinfo.ra1.xlmc.sec.miui.com -ad.jp.doubleclick.net -ad.jsnbrynb.com -adlaunch.moji.com -adlog.flurry.com -adm.baidu.com -admd.yam.com -admeld.adnxs.com -adm.icast.cn -adm.leju.sina.com.cn -ad.netowl.jp -adnet.sohu.com -adn.insight.ucweb.com -adobe-dns-2.adobe.com -adobe-dns-3.adobe.com -adobe-dns.adobe.com -a.domob.cn -a.dounanhuahui.com -adping.qq.com -ad.player.baidu.com -adpm.app.qq.com -adpro.pro.cn -adpublish.ydstatic.com -ad.qq.com -ad.qun.qq.com -adrdir.qq.com -adres.myaora.net -ads1.msads.net -ads1.msn.com -ads2.msads.net -ads2.msn.com -ads.979799777.com -ads.adaptv.advertising.com -ads.bssdl.kugou.com -adscdn.baidu.com -adsclick.qq.com -adsco.re -ads.creative-serving.com -adsence.sogou.com -adsense.html5.qq.com -adserve2.tom.com -adserver.adtech.de -adserver.adtechus.com -adserver-us.adtech.advertising.com -adserver.xpanama.net -adserver.yahoo.com -adservice.google.com -ads.eu.msn.com -adsfile.qq.com -ads.flurry.com -adsfs.oppomobile.com -ads.gionee.com -ads.gmodules.com -ads.google.com -adsgroup.qq.com -adshmct.qq.com -adshmmsg.qq.com -adsina.allyes.com -adslvfile.qq.com -adslvseed.qq.com -adsmogo.com -ads.msn.com -ads.ninemsn.com.au -adsolution.imtt.qq.com -ads.pubmatic.com -adsp.xunlei.com -adsqqclick.qq.com -adsrich.qq.com -ads.servebom.com -ads.service.kugou.com -ads.sina.com -ads.stickyadstv.com -adss.yahoo.com -adstextview.qq.com -adstream.123.sogoucdn.com -adsunflower.com -adsview2.qq.com -adsview.qq.com -ads.waps.cn -ads.wapx.cn -ads-west-colo.adsymptotic.com -ads.yahoo.com -ads.yam.com -ads.yimg.com -adsyndication.msn.com -ads.youtube.com -adtest.adwo.com -ad.toutiao.com -adtrack.ucweb.com -adui.tg.meitu.com -adv2.downsave.com -adv.app.qq.com -adver.qq.com -adview.cn -adv.sec.miui.com -ad.wang502.com -ad.winrar.com.cn -ad.wretch.cc -adx.ads.oppomobile.com -adx.g.doubleclick.net -adx.pro.cn -ad.xxguan.cn -adx.xiaodutv.com -adzjvnet.allyes.com -ad.zuimeitianqi.com -ae.bdstatic.com -aecpm.alicdn.com -a.epinv.com -aeventlog.beacon.qq.com -afd.baidu.com -afd.l.google.com -afp.adchina.com -afp.alicdn.com -afpeng.alimama.com -afpmm.alicdn.com -afpssp.alimama.com -afptrack.alimama.com -afs.googlesyndication.com -a.global.msads.net -agoodm.m.taobao.com -a.hl.mi.com -a.holagames.com -aider-res.meizu.com -a.img.static.youmi.net -ai.m.taobao.com -ai.taobao.com -ai.yimg.jp -aka-cdn.adtechus.com -aka-cdn-ns.adtechus.com -alimama.alicdn.com -alissl.ucdl.pp.uc.cn -alitui.weibo.com -aliunion.cn.yahoo.com -allnews.uodoo.com -allyesbjafa.allyes.com -allyes.com -allyesshafa.allyes.com -alog.umengcloud.com -alog.umeng.co -alog.umeng.com -alpha.brand.sogou.com -als.baidu.com -ama.adwo.com -amdc.m.taobao.com -am.g.ireader.com -a.m.shuhuangge.org -analy.qq.com -analytics3.starschina.com -analytics.ad.daum.net -analytics.msn.com -analytics.r.msn.com -analytics.snssdk.com -analytics.spotify.com -analytics.yam.com -andmlbf.tj.ijinshan.com -android-lrcresource.wps.cn -android.push.126.net -android.rqd.qq.com -an.m.liebao.cn -an.yandex.ru -aos.wall.youmi.net -api0.tuisong.baidu.com -api1.tuisong.baidu.com -api2.play.cn -api2.tuisong.baidu.com -api3.tuisong.baidu.com -api4.tuisong.baidu.com -api5.tuisong.baidu.com -api6.tuisong.baidu.com -api7.tuisong.baidu.com -api8.tuisong.baidu.com -api9.tuisong.baidu.com -api.admob.com -api.ad.xiaomi.com -api.airpush.com -api.appjiagu.com -api.apps.sina.cn -api.bailingjiankang.com -apiconfig.adwo.com -api.cpu.baidu.com -api.domob.cn -api.doumob.com -api.dreamfull.cn -api-flow.flyme.cn -api.g1.junfull.com -api.g2.junfull.com -api.iapps.ifeng.com -api.iimedia.cn -api.jr.mi.com -api.koudaikj.com -api.mobula.sdk.duapps.com -api.mp.uc.cn -api.newad.ifeng.com -api.open.uc.cn -api.primecaster.net -api-push.meizu.com -api.ra2.xlmc.sec.miui.com -api.sec.miui.com -api.share.baidu.com -api-shoulei-ssl.xunlei.com -api.shuaji.360.cn -api.so.lianmeng.360.cn -apistaging.airpush.com -api.tr.blismedia.com -api.tuisong.baidu.com -api.tw06.xlmc.sec.miui.com -api.wapa.taobao.com -api.waptest.taobao.com -api.wsq.umeng.com -api.youxiaoad.com -apk.static.youmi.net -apns.ios.ijinshan.com -apoll.m.taobao.com -app01.nodes.gslb.mi-idc.com -app02.nodes.gslb.mi-idc.com -app.50bang.org -appc.baidu.com -appdownload.alicdn.com -appgift.sinaapp.com -applog.uc.cn -apppic.yingyongbei.com -appsupdate.sinaapp.com -appsupport.qq.com -app.uu.cc -appwall.api.airpush.com -app.waps.cn -app.wapx.cn -a.qiao024.com -ard.yahoo.co.jp -aries.mzres.com -art.theta.sogoucdn.com -asearch.alicdn.com -astat.bugly.qq.com -a.stat.xiaomi.com -astrategy.beacon.qq.com -as.trklinklog.com -atanx2.alicdn.com -atanx.alicdn.com -a.tanx.com -athena.wan.sogou.com -a.tribalfusion.com -at.umeng.com -a.union.mi.com -au.umeng.co -au.umeng.com -au.youmi.net -a.waczt.cn -ax.ggfeng.com -a.youdao.com -azabu-u.ac.jp -b1sync.zemanta.com -badad.googleplex.com -b.ads1.msn.com -baichuan.baidu.com -baidu.greenxf.cn -baidutv.baidu.com -banner.alimama.com -banner.img.static.youmi.net -b.appjiagu.com -bar.baidu.com -bazinga.mse.sogou.com -b.bdstatic.com -bce.baidu.com -bc.geocities.yahoo.co.jp -bcjjg.bugsevent.com -bcjxf.bugsevent.com -b.clkservice.youdao.com -bd1.dopa.com -bd1.pipaw.com -bd2.dopa.com -bd2.pipaw.com -bdapi.ads.oppomobile.com -bdd.hainan.net -bdimg.share.baidu.com -bd-js.baixing.net -b.domob.cn -bdplus.baidu.com -bd-s.baixing.net -bds.hainan.net -bd.soarfi.cn -beacon.gtimg.com -beacon.sina.com.cn -beap.adss.yahoo.com -beap-bc.yahoo.com -beha.ksmobile.com -b.epinv.com -bes-progfree.com -beta.airpush.com -bh.contextweb.com -bid.adview.cn -bid.g.doubleclick.net -bigdata.adfuture.cn -bigdata.adsunflower.com -bigdata.adups.com -bigdata.advmob.cn -bite.theta.sogoucdn.com -biz.live.xunlei.com -biz.weibo.com -bj.bcebos.com -bjdnserror1.wo.com.cn -bjdnserror2.wo.com.cn -bjdnserror3.wo.com.cn -bjdnserror4.wo.com.cn -bjdnserror5.wo.com.cn -bjdnserror6.wo.com.cn -bjdnserror7.wo.com.cn -blogad01.myweb.hinet.net -blogad02.myweb.hinet.net -bm.alimama.cn -bmvip.alimama.cn -bobo.163.com -bokee.allyes.com -bp.mobad.ijinshan.com -brand.sogou.com -bro.flyme.cn -bsiet.husky.sogou.com -bs.l.qq.com -bss.pandora.xiaomi.com -btlaunch.baidu.com -bttrack.com -bugreportv2.qq.com -buyimg.bianxianmao.com -c0.ifengimg.com -c1.adform.net -c1.ifengimg.com -c1.minisplat.cn -c1.popads.net -c2.l.qq.com -c2.popads.net -c.35kds.com -c-adash.m.taobao.com -c.admaster.com.cn -c.admob.com -c.adsco.re -c.alimama.com -cal.meizu.com -calopenupdate.comm.miui.com -c.amazon-adsystem.com -canvas.gdt.qq.com -c.appjiagu.com -carbonads.net -cas.pxl.ace.advertising.com -cast.ra.icast.cn -catalog.video.msn.com -c.baidu.com -cb.alimama.cn -cb.baidu.com -c.betrad.com -c.biz.weibo.com -cbjs.baidu.com -cb.l.qq.com -c.bxb.oupeng.com -ccclub.cmbchina.com -cdn0.mobmore.com -cdn2.moji002.com -cdn.adnxs.com -cdn.ads.jlscds.com -cdn.adsk2.co -cdn-ads.oss-cn-shanghai.aliyuncs.com -cdn.adstract.com -cdn.ad.xiaomi.com -cdn.ark.qq.com -cdn.districtm.io -cdn.dragonstatic.com -cdn.fastclick.net -cdn.flurry.com -cdnimg.liehu.ijinshan.com -cdn.lu.sogoucdn.com -cdn.newapi.com -cdn.optaim.com -cdn.popcash.net -cdn.popmyads.com -cdn.puata.info -cdn.taboolasyndication.com -cdn.tanx.com -c.domob.cn -cee1.iteye.com -cee2.iteye.com -cf8d.stat.gw.youmi.net -cfg.imtt.qq.com -c.gdt.qq.com -cgi.connect.qq.com -chance.adsensor.org -channel.fanxing.kugou.com -cjhq.baidu.com -cj.qidian.com -cjroq.bealge.sogou.com -cl.he9630.com -click1forhz.adsmogo.com -click2forhz.adsmogo.com -click3forhz.adsmogo.com -click.admaster.com.cn -click.bes.baidu.com -click.hm.baidu.com -click.jebe.renren.com -click.mz.simba.taobao.com -click.qianqian.com -clicks.beap.bc.yahoo.com -click.simba.taobao.com -click.stat.hao.360.cn -click.tanx.com -click.tianyaui.com -click.tz.simba.taobao.com -click.uve.mobile.sina.cn -click.wrating.com -client.show.qq.com -client.stats.yinyuetai.com -clk.optaim.com -clkservice2.dict.youdao.com -clkservice.mail.youdao.com -clkservice.union.youdao.com -clkservice.youdao.com -cloudcdn.dopa.com -cloudcdn.dopa.com.cn -cloudcdn.yousee.com -cloudservice22.kingsoft-office-service.com -c.l.qq.com -cm066.getui.igexin.com -cm.adgrx.com -cm.adkmob.com -cm.admaster.com.cn -cm.baichuan.baidu.com -cm.baidu.com -cmcdl.cmcm.com -cmc.tanx.com -cm.ctnsnet.com -cm.dmp.sina.cn -cm.e.qq.com -cm.eyereturn.com -cm.g.doubleclick.net -c.minisplat.cn -cm.ipinyou.com -cm.l.qq.com -cm.netseer.com -cm.p4p.cn.yahoo.com -cm.pos.baidu.com -cms.an.m.liebao.cn -c.msn.com -c.msn.com.cn -cms.opendsp.tanx.com -cms.quantserve.com -cms.tanx.com -cnzz.mmstat.com -code.fastclick.net -coed-d.openx.net -col.hztags.net -config.adsage.cn -config.adsage.com -config.adview.cn -config.mobisage.cn -config.push.sogou.com -contentrecommend-out.mobile.sina.cn -conversion.adpro.cn -conversion.pro.cn -conv.youdao.com -cookiemapping.wrating.com -coro.benbaisteel.com -couchcoaster.jp -count10.51yes.com -count11.51yes.com -count12.51yes.com -count13.51yes.com -count14.51yes.com -count15.51yes.com -count16.51yes.com -count17.51yes.com -count18.51yes.com -count19.51yes.com -count20.51yes.com -count21.51yes.com -count22.51yes.com -count23.51yes.com -count24.51yes.com -count2.51yes.com -count25.51yes.com -count26.51yes.com -count27.51yes.com -count28.51yes.com -count29.51yes.com -count30.51yes.com -count31.51yes.com -count32.51yes.com -count33.51yes.com -count34.51yes.com -count3.51yes.com -count35.51yes.com -count36.51yes.com -count37.51yes.com -count38.51yes.com -count39.51yes.com -count40.51yes.com -count41.51yes.com -count42.51yes.com -count43.51yes.com -count44.51yes.com -count4.51yes.com -count45.51yes.com -count46.51yes.com -count47.51yes.com -count48.51yes.com -count49.51yes.com -count50.51yes.com -count51.51yes.com -count.51yes.com -count5.51yes.com -count6.51yes.com -count7.51yes.com -count8.51yes.com -count9.51yes.com -counter.kingsoft.com -counter.sina.com.cn -count.mail.163.com -countt.51yes.com -count.video.sina.com.cn -cpc.brand.sogou.com -cpc.click.alimama.com -cpc.sogou.com -cpro2.baidu.com -cpro2.baidustatic.com -cpro.baidu.com -cpro.baidustatic.com -cpro.tieba.baidu.com -cpro.zhidao.baidu.com -cpu-admin.baidu.com -cpu.baidu.com -cpv.ty229.com -creative.1111cpc.com -creative.jdkic.com -creative.ltheanine.cn -cre-dp.sina.cn -cre.dp.sina.cn -cre.mix.sina.com.cn -crl.microsoft.com -crm-eve.b2b.alibaba-inc.com -cr.m.liebao.cn -cr-p16.ladsp.com -crs.baidu.com -cs.dqwjzm.com -cti.w55c.net -ct.niu.xunlei.com -cupid.jebe.renren.com -c.wcpt.biz.weibo.com -c.wrating.com -cws-cctv.conviva.com -cz01016102.ms758.com -d00.sina.com.cn -d0.sina.com.cn -d1grtyyel8f1mh.cloudfront.net -d1.sina.com.cn -d1.sinaimg.cn -d2.sina.com.cn -d31qbv1cthcecs.cloudfront.net -d36eyd5j1kt1m6.cloudfront.net -d3g.qq.com -d3.sina.com.cn -d3.sinaimg.cn -d3v1lb83psg9di.cloudfront.net -d4.sina.com.cn -d5nxst8fruw4z.cloudfront.net -d5p.de17a.com -d5.sina.com.cn -d6.sina.com.cn -d6.sinaimg.cn -d7.sina.com.cn -d7.sinaimg.cn -d8.sina.com.cn -d8.sinaimg.cn -d9.sina.com.cn -daohang.114so.cn -data.ads.oppomobile.com -data.flurry.com -data.mistat.xiaomi.com -datax.baidu.com -dcads.sina.com.cn -d.clkservice.youdao.com -dc.meitustat.com -dd713.bj.bcebos.com -dd.iask.cn -dd.iaskgo.com -ddkkrrla.m.qxs.la -d.domob.cn -ddrrccck.m.qxs.la -delivery.dmkt-sp.jp -de.pandora.xiaomi.com -dev.tg.wan.360.cn -dev.umeng.com -dfc1.benbaisteel.com -df.tanx.com -d.gdt.qq.com -dh.holaworld.cn -dir.minigame.qq.com -discuz.gtimg.cn -display.360totalsecurity.com -display.ad.daum.net -display.adhudong.com -disqusads.com -d.kugou.com -dl.9xu.com -dl.client.baidu.com -dl.cm.ksmobile.com -dl.jianshunrui.com -dl.kjava.sina.cn -dl.sybspools.com -dl.union.ijinshan.com -dl.uu.cc -dl.wan.sogoucdn.com -dl.youjia2016.com -dm.bytedance.com -dmp.adpush.cn -dmpclick.deliver.ifeng.com -dm.pstatp.com -dol.deliver.ifeng.com -dolphin.deliver.ifeng.com -dol.tianya.cn -domob.cn -domob.com.cn -dorangesource.alicdn.com -down.dashendown.com -download.adsage.com -download.zhushou.sogou.com -dp.559.cc -dp.g.doubleclick.net -dp.im.weibo.cn -dpm.demdex.net -dps.wtdtjs.com -dressimage.img-cn-beijing.aliyuncs.com -dr.holaworld.cn -drmcmm.baidu.com -ds.jlbksy.com -dsp.adfarm1.adition.com -dsp.brand.sogou.com -ds-pc.admsger.com -dspcm.brand.sogou.com -dsp-impr2.youdao.com -dsp-impr.youdao.com -dsp.pro.cn -dsp.simba.taobao.com -dsp.youdao.com -dt.adsafeprotected.com -dualstack.adsame-1421766300.ap-southeast-1.elb.amazonaws.com -d.union.ijinshan.com -dup.baidustatic.com -dvb.pandora.xiaomi.com -dvsend.china.com -dvser02.china.com -dvser.china.com -dvx-android.domob.cn -dwtrack.qidian.com -dxp.baidu.com -dxprla.m.qxs.la -dzl.baidu.com -e7free.allyes.com -e.admob.com -e.ad.xiaomi.com -eastmoney.allyes.com -e.baidu.com -ebook.res.meizu.com -eclick.baidu.com -ecma.bdimg.com -ecmb.bdimg.com -ecmc.bdimg.com -ecpm.tanx.com -e.domob.cn -e.domob.com.cn -eee.eh39.co -eee.kj78.org -eee.ttyy888.co -e.emgwq.com -ef-dongfeng.tanx.com -eff.inte.sogou.com -eff.lu.sogou.com -ef.opendsp.tanx.com -ef.tanx.com -e.hellomingpian.com -eiv.baidu.com -ejzr.golden1.sogou.com -engine.lvehaisen.com -engine.tuia.cn -entry.adsage.com -entry.baidu.com -e-p4p.163.com -e.qq.com -ereg.adobe.com -ereg.wip3.adobe.com -e.romgv.com -ers.baidu.com -etg.qq.com -etl.xlmc.sandai.net -etl.xlmc.sec.miui.com -et.tanx.com -event.ksosoft.com -eventlog.beacon.qq.com -events.pingan.com -ex.mobmore.com -exp.3g.ifeng.com -ex.puata.info -ex.qq.com -ex.tanx.com -extmoney.i1608.com -ex.umengcloud.com -e.yangjingbang.net -ezine.oupeng.com -f10.baidu.com -f1.luoshenbest.cn -f1.p0y.cn -f2.p0y.cn -f3.mi-stat.gslb.mi-idc.com -fa.163.com -fair.sogou.com -fanxing.kugou.com -fav.simba.taobao.com -fcanr.tracking.miui.com -fclick.baidu.com -fd.anzhi.com -f.domob.cn -fds.api.moji.com -feedback.whalecloud.com -feed.baidu.com -feed.theta.sogou.com -fexclick.baidu.com -file.ipinyou.com.cn -files2.sogou.com -files.adform.net -fim.adnxs.com -flurry.cachefly.net -flurry.com -fm.ipinyou.com -fm.p0y.cn -fm.qzone.qq.com -fms.ipinyou.com -focusbaiduafp.allyes.com -fodder.qq.com -fodder.tc.qq.com -folder.adfuture.cn -folder.adsunflower.com -folder.advmob.cn -fota4.adups.cn -fotacontrol.adfuture.cn -fs.uc.nearme.com.cn -fs-uc-nearme-com-cn.oss-cn-hangzhou.aliyuncs.com -fw.adsafeprotected.com -fw.qq.com -fxc.aiquxs.com -fych.uranus.sogou.com -g1.163.com -g.163.com -g1.tagtic.cn -g.adnxs.com -galaxy.bjcathay.com -galaxy.sogoucdn.com -gam.adnxs.com -gamead.swjoy.com -game.adwo.com -gamebox.kugou.com -game.html5.qq.com -game.subway.uu.cc -game.weibo.cn -game.weibo.com -g.baidu.com -gb.corp.163.com -g.domob.cn -gemini.yahoo.com -gen.alicdn.com -geo.moatads.com -get.sogou.com -ggle.lywf.me -g.haluoha.com -gif.lu.sogoucdn.com -gimg.baidu.com -gload.adhood.com -global.msads.net -gma.alicdn.com -go.mmstat.com -googleadservices.com -googleads.g.doubleclick.net -googleadsserving.cn -googlecommerce.com -googlesyndication.com -googletagmanager.com -googletagservices.com -goto.sogou.com -green.erne.co -g.sdk.look.360.cn -guanjia.baidu.com -guess.union2.50bang.org -gu.qlogo.cn -g.w5b454.com -gw5.push.mcp.weibo.cn -gw6.push.mcp.weibo.cn -g.wrating.com -gxb.mmstat.com -gxe.husky.sogou.com -h5.holalauncher.com -haitaoad.nosdn.127.net -hao549.com -hao.7654.com -hao.qquu8.com -haostat.qihoo.com -hao.uc.cn -hbdnserror1.wo.com.cn -hbdnserror2.wo.com.cn -hbdnserror3.wo.com.cn -hbdnserror4.wo.com.cn -hbdnserror5.wo.com.cn -hbdnserror6.wo.com.cn -hbdnserror7.wo.com.cn -hbdt.luomi.com -hc.baidu.com -h.domob.cn -hivedata.cc -hk.jtsh123.com -hk.napi.ucweb.com -hl2rcv.adobe.com -hljdnserror1.wo.com.cn -hljdnserror2.wo.com.cn -hljdnserror3.wo.com.cn -hljdnserror4.wo.com.cn -hljdnserror5.wo.com.cn -hlrcv.stage.adobe.com -hm.baidu.com -hm.l.qq.com -hmma.baidu.com -h.msn.com -hndnserror1.wo.com.cn -hndnserror2.wo.com.cn -hndnserror3.wo.com.cn -hndnserror4.wo.com.cn -hndnserror5.wo.com.cn -hndnserror6.wo.com.cn -hndnserror7.wo.com.cn -hot.browser.miui.com -hpd.baidu.com -hs.qhupdate.com -httpdns.push.oppomobile.com -httpring.qq.com -ht.www.sogou.com -hub5pn.wap.sandai.net -huichuan.sm.cn -huid.ad.360.cn -huodong.ios.shouji.360.cn -hydra.alibaba.com -hyfh.benbaisteel.com -hz.mmstat.com -i1.go2yd.com -iadctest.qwapi.com -iad.g.163.com -ib.adnxs.com -ibn.adnxs.com -iclick.cm.admaster.com.cn -i.clkservice.youdao.com -idigger.allyes.com -idm.bce.baidu.com -idm-su.baidu.com -i.domob.cn -i.dreamfull.cn -ids1.deliver.ifeng.com -ids.deliver.ifeng.com -idx.m.hub.sandai.net -iebar.baidu.com -ieonline.microsoft.com -ifengad.3g.ifeng.com -i.flow.browser.oppomobile.com -iflow.uczzd.cn -iflow.uczzd.com -iflow.uczzd.com.cn -iflow.uczzd.net -if.mingxing.qq.com -ifs.tanx.com -i.gdt.qq.com -i.haloapps.com -i.holalauncher.com -i.huilixieye.net -i.ipinyou.com -iis1.deliver.ifeng.com -iis3g.deliver.ifeng.com -ikcode.baidu.com -image.box.xiaomi.com -image.p4p.sogou.com -images.fastclick.net -image.zzd.sm.cn -imagzine.oppomobile.com -imc.l.qq.com -img01.taotaosou.cn -img0.egou.com -img1.126.net -img1.gtimg.com -img1.km.com -img1.pcfg.cache.wps.cn -img1.sj.qq.com -img2.126.net -img2.km.com -img3.km.com -img.adbox.sina.com.cn -img.adnyg.com -img.adnyg.com.w.kunlungr.com -img-ad.oupeng.com -img.adpush.cn -img.ad.zhangyue.com -img.alimama.cn -img.amp.ad.sina.com.cn -img-cdn-spot.ymcdn.cn -img.dawenxue.org -img-dsp.oss-cn-beijing.aliyuncs.com -img.shouji.sogou.com -img.taotaosou.cn -img.toppr.com.cn -img.wan.sogou.com -i.mmcdn.cn -imp.optaim.com -impservice2.youdao.com -impservice.chnl.youdao.com -impservice.dictvista.youdao.com -impservice.dictweb.youdao.com -impservice.dictword.youdao.com -impservice.dict.youdao.com -impservice.mail.youdao.com -impservice-test.dictapp.youdao.com -impservice.union.youdao.com -impservice.youdao.com -in1.feed.uu.cc -in1.secure.uu.cc -info.3g.qq.com -info.analysis.kp.sec.miui.com -infocenter.meizu.com -info.downsave.com -info.gomlab.com -info.pinyin.sogou.com -info.sec.miui.com -info.yitsoftware.com -input.shouji.sogou.com -inside.bitcomet.com -install2.kugou.com -install.kugou.com -int.dpool.sina.com.cn -interest.mix.sina.com.cn -inte.sogou.com -inte.theta.sogoucdn.com -ipinyou.com -ir.mail.163.com -irnvf.lu.sogou.com -irpmt.mail.163.com -i.stat.nearme.com.cn -i.w55c.net -iwan.sogou.com -j.appjiagu.com -jct.maptu.cn -j.domob.cn -jebe.renren.com -jebe.xnimg.cn -jellyfish.pandora.xiaomi.com -j.hongyangpai.com -jiayi1.oss-cn-shanghai.aliyuncs.com -jifen.2345.com -jingjia.qq.com -jldnserror1.wo.com.cn -jldnserror2.wo.com.cn -jldnserror3.wo.com.cn -jldnserror4.wo.com.cn -jldnserror5.wo.com.cn -jpg.inte.sogoucdn.com -jpush.html5.qq.com -jqmt.qq.com -js.50bang.org -js.51taifu.com -jsadsdisplay.cn-beijing.log.aliyuncs.com -js-apac-ss.ysm.yahoo.com -js.icast.cn -jsnp.golden1.sogou.com -js.pub.tom.com -jsqmt.qq.com -js.stat.ijinshan.com -js.union-wifi.com -juxiao.adsmogo.net -j.wan.liebao.cn -j.wit.qq.com -jxlog.istreamsche.com -k.domob.cn -keystone.mwbsys.com -kgmobilestat.kugou.com -kn.zzdahan.com -kr.sybspools.com -kstj.baidu.com -kthxd.lu.sogou.com -ktivn.uranus.sogou.com -kuaikan.netmon.360safe.com -kv.stat.nearme.com.cn -kw.ra.icast.cn -kwurl.ucweb.com -l2.l.qq.com -labs.ra.icast.cn -lb.gtimg.com -lb.l.qq.com -lcs.dev.surepush.cn -l.domob.cn -leak.360.cn -ledou.dl.uu.cc -letv.allyes.com -letv.axp.admaster.com.cn -letv.cm.admaster.com.cn -links.services.disqus.com -livec.l.qq.com -livem.l.qq.com -livep.l.qq.com -lives.l.qq.com -lk.brand.sogou.com -lm.dawenxue.org -l.minisplat.cn -lm.licenses.adobe.com -lmlicenses.wip4.adobe.com -lm.souid.com -lndnserror1.wo.com.cn -lndnserror2.wo.com.cn -lndnserror3.wo.com.cn -lndnserror4.wo.com.cn -lndnserror5.wo.com.cn -lndnserror6.wo.com.cn -lndnserror7.wo.com.cn -log.collect.yinyuetai.com -log.cs.pp.cn -log.mix.sina.com.cn -log.music.baidu.com -logs.newapi.com -log.spotify.com -log.stat.kugou.com -log.tagtic.cn -log.tbs.qq.com -log.umsns.com -log.umtrack.com -logupdate.avlyun.sec.miui.com -log.vcgame.cn -log.web.kugou.com -l.qq.com -ls.l.qq.com -lu.sogou.com -lxcdn.dl.files.xiaomi.net -m1.baidu.com -m.28487.net -m3bnqqqw.com -m.7180443.com -m-78.jp -ma.baidu.com -m-adash.m.taobao.com -mad.m.maxthon.cn -m.adpro.cn -mads.amazon-adsystem.com -m.adxpop.com -m.ad.zhangyue.com -magnetic.t.domdex.com -m.airpush.com -map.media6degrees.com -mapp.qzone.qq.com -masdk.3g.qq.com -master.wap.dphub.sandai.net -match.adsby.bidtheatre.com -match.adsrvr.org -match.p4p.1688.com -match.prod.bidr.io -match.rundsp.com -material.istreamsche.com -material.mtty.xin -maw.wnbfw.com -maxwebsearch.com -mazu.3g.qq.com -m.bailingjiankang.com -m.beacon.sina.com.cn -mbs.hao.360.cn -m.bss.pandora.xiaomi.com -mb.yidianzixun.com -mclick.simba.taobao.com -m.clkservice.youdao.com -mcore.vcgame.cn -mc.yandex.ru -md.1drj.com -mdap.alipaylog.com -mdc.meitustat.com -md.he9630.com -m.domob.cn -mdrecv.app.cntvwb.cn -md.sh5e.com -m.duobao999.com -medal.blog.csdn.net -media.admob.com -media.fastclick.net -mediapro.pro.cn -media.trafficfactory.biz -m.ee-skin.com -m.ee-vip.net -meitubeauty.meitudata.com -m.emgwq.com -metok.sys.miui.com -m.fhxsw.org -mfp.deliver.ifeng.com -m.game.weibo.cn -m.gdt.vip1790.cn -mg.games.sina.com.cn -m.guanren9.com -m.hellomingpian.com -migc.g.mi.com -migcreport.g.mi.com -mi.gdt.qq.com -migrate.driveapi.micloud.xiaomi.net -minfo.wps.cn -mini2015.qq.com -mini.cpc.sogou.com -mini.jijiplayer.com -mipcache.bdstatic.com -m.irs01.com -mis.g.mi.com -mivideo.g.mi.com -m.kubiqq.com -m.laojiayoufang.com -mlb.did.ijinshan.com -mlog.search.xiaomi.net -m.lu.sogou.com -mm.admob.com -mm.dopa.com.cn -mmv.admob.com -mobaders.oss-cn-beijing.aliyuncs.com -mobads.baidu.com -mobads-logs.baidu.com -mobi.adsage.com -mobileads.google.com -mobileads.msn.com -mobilelog.kugou.com -mo.haloapps.cn -moka.inte.sogoucdn.com -monitor.uu.qq.com -mo.res.wpscdn.cn -mostat.wps.cn -mo.test.haloapps.com -motu.p4p.sina.com.cn -mou.niu.xunlei.com -moupdate10332052.wps.cn -mpb1.iteye.com -mpb2.iteye.com -mpp.vindicosuite.com -mpro.baidu.com -m.qpic.cn -mqqad.cs0309.html5.qq.com -mqqad.html5.qq.com -mqqadr.reader.qq.com -m.romgv.com -ms.cmcm.com -msg.shouji.360.cn -msg.umengcloud.com -m.simba.taobao.com -msite.baidu.com -m.sjzhushou.com -msnclick.wrating.com -msn.wrating.com -mso.allyes.com -mti.35kds.com -mtty-cdn.mtty.xin -m.uc123.com -m.uczzd.cn -musik-mp3.info -mvads.kugou.com -mw.adwo.com -m.wrating.com -mws.adsage.com -my.adsmogo.com -m.yangjingbang.net -n.3g.163.com -na1r.services.adobe.com -na2m-pr.licenses.adobe.com -na.ads.yahoo.com -nai.cpxkvc.com -n.a.mosenni.com -navi.gd.chinamobile.com -nbsdk-baichuan.alicdn.com -nbsdk-baichuan.taobao.com -nc004x.corp.youdao.com -nc045x.corp.youdao.com -n.domob.cn -neirong.baidu.com -net.adpush.cn -new.ltheanine.cn -news.766ba.net -news.mpush.qq.com -news.push.126.net -newspush.sinajs.cn -nex.163.com -nfdnserror10.wo.com.cn -nfdnserror11.wo.com.cn -nfdnserror12.wo.com.cn -nfdnserror13.wo.com.cn -nfdnserror14.wo.com.cn -nfdnserror15.wo.com.cn -nfdnserror16.wo.com.cn -nfdnserror17.wo.com.cn -nfdnserror1.wo.com.cn -nfdnserror2.wo.com.cn -nfdnserror3.wo.com.cn -nfdnserror4.wo.com.cn -nfdnserror5.wo.com.cn -nfdnserror6.wo.com.cn -nfdnserror7.wo.com.cn -nfdnserror8.wo.com.cn -nfdnserror9.wo.com.cn -n.gemini.yahoo.com -nhz.adwo.com -nichibenren.or.jp -nicorette.co.kr -nop.xpanama.net -notice.game.xiaomi.com -notifiter.youmi.net -notify.oupeng.com -novelsns.html5.qq.com -nsclick.baidu.com -nsclickvideo.baidu.com -o2o.api.xiaomi.com -oascentral.sina.com -oascentral.sina.com.hk -obeyter.com -oc.umeng.co -oc.umeng.com -o.domob.cn -offline-adv.oray.com -o.if.qidian.com -oimagea2.ydstatic.com -omg.inte.sogoucdn.com -omgmta1.qq.com -omgmta.qq.com -o.minisplat.cn -onetag-sys.com -opehs.tanx.com -openapi.guanjia.qq.com -openapi-news.meizu.com -openbox.mobilem.360.cn -open.play.cn -openrcv.baidu.com -ope.tanx.com -oppo.yidianzixun.com -optimus.ipinyou.com -osc.uranus.sogou.com -osfota.cdn.aliyun.com -osupdateservice.yunos.com -otf.msn.com -oth.eve.mdt.qq.com -oth.str.mdt.qq.com -oth.update.mdt.qq.com -ow.s1.shuhuangge.org -ow.s2.shuhuangge.org -p2.l.qq.com -p3.l.qq.com -p3p.mmstat.com -p3p.sogou.com -p3p.yahoo.com -p4psearch.china.alibaba.com -p8u.hinet.net -p.admob.com -padsdel2.cdnads.com -p.adsymptotic.com -pagead2.googlesyndication.com -pagead46.l.doubleclick.net -pagead.google.com -pagead.l.google.com -pagead-tpc.l.google.com -pagechoice.net -pagespeed.report.qq.com -p.alimama.com -palmnews.sina.cn -p.appjiagu.com -parking.zunmi.cn -partnerad.l.google.com -partnerads.ysm.yahoo.com -partner.googleadservices.com -pat.farvd.com -patriot.cs.pp.cn -pb3.pstatp.com -pbd.sogou.com -pb.sogou.com -pb.wang502.com -pcfg.wps.cn -p.clkservice.youdao.com -pcookie.tanx.com -pc.quansj.cn -pc.videoclick.baidu.com -pcxzo.pluto.sogou.com -pdc.micloud.xiaomi.net -pd.dopa.com.cn -pdl.gionee.com -pd.ok365.com -p.domob.cn -pegasus.cmcm.com -pfpip.sina.com -pfp.sina.com.cn -pgdt.gtimg.cn -pgdt.ugdtimg.com -photobucket.adnxs.com -phs.tanx.com -pic.517m.cn -pic.51yes.com -pics.taobaocdn.com -pindao.huoban.taobao.com -ping.acc.sogou.com -pingfore.qq.com -pingfore.tenpay.com -pinghot.qq.com -pingma.qq.com -ping.pinyin.sogou.com -p.inte.sogou.com -pixel.adsafeprotected.com -pixel.advertising.com -pixel-a.sitescout.com -pixel.mathtag.com -pixel.rubiconproject.com -pixel.sitescout.com -pixel.tapad.com -pix.impdesk.com -pjyu.golden1.sogou.com -pkg-cdn.youmi.net -p.kjwx8.com -playinfo.gomlab.com -p.l.qq.com -p.lu.sogou.com -pmc-d.openx.net -pmir.3g.qq.com -pms.mb.qq.com -png.lu.sogoucdn.com -popme.163.com -pop.sjk.ijinshan.com -popup.msn.com -pos.baidu.com -post.ra.icast.cn -pptv.adx.admaster.com.cn -pptv.cm.admaster.com.cn -ppurifier.game.xiaomi.com -practivate.adobe.com -pr-bh.ybp.yahoo.com -pre.api.tw06.xlmc.sandai.net -pre.ra.icast.cn -p.rfihub.com -pro.cn -prom.gome.com.cn -promote.biz.weibo.cn -promotion.aliyun.com -promotion.gomlab.com -proton.flurry.com -proxy.sec.miui.com -pr.ybp.yahoo.com -p.sdu8cvc.com -psfq.gou.sogou.com -p.store.qq.com -p.tanx.com -p.tencentmind.com -pubads.g.doubleclick.net -pub.pxl.ace.advertising.com -pub.se.360.cn -puds.test.uae.uc.cn -puds.ucweb.com -pull.push.sogou.com -pups.bdimg.com -push.5z5zw.com -pushapi.lenovomm.com -push.mobile.kugou.com -push.res.meizu.com -push.wandoujia.com -push.yuedu.163.com -push.zhangyue.com -push.zhanzhang.baidu.com -pv.anzhi.com -pv.focus.cn -pv.ra.icast.cn -pv.sogou.com -pvstat.html5.qq.com -pwj.biqugezw.com -px.adhigh.net -pxl.connexity.net -pxl.idx.admaster.com.cn -px.moatads.com -px.owneriq.net -px.powerlinks.com -py2.qlogo.cn -py.qlogo.cn -q.domob.cn -qhl.bealge.sogou.com -qianclick.baidu.com -q.i.gdt.qq.com -qqshow2-item.qq.com -qss-client.qq.com -qt002x.corp.youdao.com -qxm.pluto.sogou.com -r.254a.com -r2.adwo.com -r3.adwo.com -rabbit.meitustat.com -rabbit.mtadvert.com -rabbit.tg.meitu.com -rad.live.com -rad.microsoft.com -r.admob.com -ra.gtimg.com -rank.hit.china.com -rbp.emea.mxptint.net -rbp.mxptint.net -r.browser.miui.com -r.bxb.oupeng.com -rc2waycm-atl.netmng.com -rcmd.pop.ijinshan.com -rcp.c.appier.net -rcv.mobad.ijinshan.com -rcv.union-wifi.com -rd.ane.yahoo.co.jp -rd.e.sogou.com -r.dmp.sina.cn -r.domob.cn -rdstat.tanx.com -rd.wan.360.cn -reader.browser.miui.com -rec.g.163.com -recmd.html5.qq.com -redirect.simba.taobao.com -referrer.disqus.com -release.baidu.com -re.m.taobao.com -report.adview.cn -rescn.u3.ucweb.com -res.icast.cn -res.ipingke.com -res.mi.baidu.com -res.mmstat.com -resolver.gslb.mi-idc.com -resolver.msg.xiaomi.net -res.qhupdate.com -re.taobao.com -rh.qq.com -rich.qq.com -rigel.baidustatic.com -river.zhidao.baidu.com -rjgw.theta.sogou.com -rj.m.taobao.com -rl.go2yd.com -rlogs.youdao.com -rmads.eu.msn.com -rmads.msn.com -r.mail.163.com -rm.gdt.qq.com -rmoeu.mercury.sogou.com -rm.ra.icast.cn -rm.sina.com.cn -r.msn.com -rmtx.ra.icast.cn -r.ow.domob.cn -rpc-php.trafficfactory.biz -rp.gwallet.com -rplog.baidu.com -rs1.qq.com -rs2.qq.com -rs.sinajs.cn -rtas.videocc.net -rtb.admaster.com.cn -rtb.adview.cn -rt.gsspat.jp -rubicon-match.dotomi.com -rub.pxl.ace.advertising.com -rudy.adsnative.com -r.youmi.net -s0.2mdn.net -s.051352.com -s1.2mdn.net -s1.cmfu.com -s2.yandui.com -s.35kds.com -s.6travel.com -sa0.tuisong.baidu.com -sa1.tuisong.baidu.com -sa3.tuisong.baidu.com -s.alitui.weibo.com -s.amazon-adsystem.com -s.appjiagu.com -sa.tuisong.baidu.com -sax1.sina.com.cn -sax2.sina.com.cn -sax3.sina.com.cn -sax4.sina.com.cn -sax5.sina.com.cn -sax6.sina.com.cn -sax7.sina.com.cn -sax8.sina.com.cn -sax9.sina.com.cn -saxn.sina.com.cn -sax.sina.com.cn -saxs.sina.com.cn -s.baidu.com -sbeacon.sina.com.cn -scc.domob.cn -scdown.qq.com -sc.ggdoubi.com -sc.ggfeng.com -schemas.android.com -sc.iasds01.com -sclick.baidu.com -s.clkservice.youdao.com -s.cpro.baidu.com -script-bd.baixing.net -sdapprecv.app.cntvwb.cn -sddnserror1.wo.com.cn -sddnserror2.wo.com.cn -sddnserror3.wo.com.cn -sddnserror4.wo.com.cn -sddnserror5.wo.com.cn -sddnserror6.wo.com.cn -sddnserror7.wo.com.cn -sddnserror8.wo.com.cn -sddnserror9.wo.com.cn -sd.domob.cn -sdkapp.mobile.sina.cn -sdkapp.uve.weibo.com -sdk.cferw.com -sdkclick.mobile.sina.cn -sdkconfig.ad.xiaomi.com -sdk.e.qq.com -sdkinit.taobao.com -sd.kk3g.net -sdklog.uu.cc -sdk.look.360.cn -sdk.mobad.ijinshan.com -sdk.open.phone.igexin.com -sdk.open.talk.gepush.com -sdk.open.talk.igexin.com -sdkpay.uu.cc -sdksitter.m.sjzhushou.com -sdl.domob.cn -sdn.kugou.com -s.domob.cn -sdownload.stargame.com -sdsp.ipinyou.com -sdu.adwo.com -sea.napi.ucweb.com -sec-cdn.static.xiaomi.net -sec.resource.xiaomi.net -secure.adnxs.com -secure.fastclick.net -secure.img-cdn.mediaplex.com -securepubads.g.doubleclick.net -secure-sin.adnxs.com -security.browser.miui.com -serve.popads.net -server.m.pp.cn -service.ad.adesk.com -service.epro.sogou.com -service.urchin.com -sestat.baidu.com -setting.snswin.qq.com -sg.a.stat.mi.com -s.gdt.qq.com -s.go2yd.com -shaft.jebe.renren.com -shama5.com -share.baidu.com -shenghuo.xiaomi.com -shiwan.dl.gxpan.cn -shizen-no-megumi.com -shop.admin.yinyuetai.com -shop.yinyuetai.com -shouji.sougou.com -show.re.taobao.com -showwxml.qq.com -simaba.taobao.com -simba.m.taobao.com -s.img.mix.sina.com.cn -sin1.g.adnxs.com -sina.allyes.com -sinas.allyes.com -sina.wrating.com -sina.yinstar.org -s.ipinyou.com -s.jlminte.com -s.l8l9.com -s.lianmeng.360.cn -slides.discovery.tom.com -slog.sina.cn -slog.sina.com.cn -sm51shai.allyes.com -smcreative.allyes.com -sm.domob.cn -smjs.allyes.com -smt.admaster.com.cn -sngmta.qq.com -snippet.pos.baidu.com -sobar.baidu.com -sobartop.baidu.com -soft.data.weather.360.cn -soft.tbs.imtt.qq.com -sohutv.cm.admaster.com.cn -sohu.wrating.com -song.fanxing.kugou.com -source.youxiaoad.com -sousuo.xm.sjzhushou.com -sp3.cndm.com -spcode.baidu.com -sp.fastclick.net -spro.so.com -s.qhupdate.com -srd.simba.taobao.com -sroomafp.allyes.com -srv.buysellads.com -srv.carbonads.net -ssdk.adkmob.com -ss.he9630.com -ssl-cdn.static.browser.mi-img.com -ssl.ymapp.com -ss.missyouxi.com -ssp.0531kt.com -ssp1.dmpdsp.com -ssp.86str.com -ssp.adpush.cn -sspapi.youxiaoad.com -ssp.chaohutechan.com -ssp.dmpdsp.com -ssp.kss.ksyun.com -ssp.pro.cn -sspservice.ad-survey.com -ssp.thescenseproject.com -ssp.youxiaoad.com -sssvd.china.com -stadig0.ifeng.com -stadig.ifeng.com -staging.admin.e.mi.com -stags.bluekai.com -sta.haloall.com -sta.holagames.com -sta.jcjk0451.com -startup.oupeng.com -stat.360safe.com -stat.browser.nearme.com.cn -stat.gw.youmi.net -staticadm.leju.sina.com.cn -static.adsafeprotected.com -static.adwo.com -static.alimama.com -static.doubleclick.net -static.flv.uuzuonline.com -static.googleadsserving.cn -static.m.sjzhushou.com -static.tzyiyuantuan.com -static.youmi.net -statis.push.netease.com -statisticsv2.yinyuetai.com -stat.m.360.cn -stat.moji.com -stat.pandora.xiaomi.com -stats.chinaz.com -stats.dmp.ghac.cn -stat.simba.taobao.com -stats.ipinyou.com -stats.umsns.com -stat.v.baidu.com -stat.zuimeitianqi.com -st.holalauncher.com -st.holaworld.cn -stjzh.gdtarget.com -strategy.beacon.qq.com -strip.alicdn.com -strip.taobaocdn.com -stuff.202m.com -st.yandexadexchange.net -su.bdimg.com -su.bdstatic.com -subswin.com -s.union.360.cn -susapi.dev.surepush.cn -susapi.lenovomm.com -swa.gtimg.com -sw.mobile.sogou.com -s.wrating.com -swx.domob.cn -sxdnserror1.wo.com.cn -sxdnserror2.wo.com.cn -sxdnserror3.wo.com.cn -sxdnserror4.wo.com.cn -sxdnserror5.wo.com.cn -sxdnserror6.wo.com.cn -sy.brand.sogou.com -sync.1rx.io -sync.adaptv.advertising.com -sync.adotmob.com -sync-dsp.ad-m.asia -sync.extend.tv -sync.fastclick.net -sync.intentiq.com -sync.ipredictive.com -sync.mathtag.com -sync.tidaltv.com -sync-tm.everesttech.net -s.youmi.net -sys.zhangyue.com -t10.baidu.com -t11.baidu.com -t12.baidu.com -tags.bluekai.com -tajs.qq.com -t.alimama.com -tanxlog.istreamsche.com -tap.rubiconproject.com -tap-t.rubiconproject.com -ta.qq.com -t.atpanel.com -tb060x.corp.youdao.com -tb104x.corp.youdao.com -t.collect.yinyuetai.com -tcss.qq.com -t.domob.cn -t-e.flyme.cn -temai.snssdk.com -temai.taobao.com -test2014.adview.cn -test.ad.xiaomi.com -test.api.xlmc.sandai.net -test.e.ad.xiaomi.com -test.surepush.cn -test.zeus.ad.xiaomi.com -textlink.simba.taobao.com -t-flow.flyme.cn -t.gdt.qq.com -tg.jifen.2345.com -theta.sogoucdn.com -tj.b.qq.com -tj.kugou.com -tjs.sjs.sinajs.cn -tj.tongjiwo.com -tj.video.qq.com -tk.baidu.com -tk.optaim.com -tkweb.baidu.com -t.l.qq.com -tmn-d.openx.net -tns.simba.taobao.com -tob-cms.bj.bcebos.com -token.rubiconproject.com -tom.allyes.com -tongji.meizu.com -tongji-res1.meizu.com -tongji.wrating.com -toolbar.baidu.com -toolbar.msn.com -tools.3g.qq.com -top.h.qhimg.com -toruk.tanx.com -toutiao.2haha.com -tpc.googlesyndication.com -tpush.html5.qq.com -trace.qq.com -track.adwo.com -track.dmp.youmi.net -track.dragonparking.com -track-east.mobileadtrading.com -tracker.baidu.com -track.eyeviewads.com -tracking.ad-survey.com -tracking.m6r.eu -tracking.miui.com -track.ra.icast.cn -track.uc.cn -trafficfactory.biz -trc.adsage.com -trends.mobile.sina.cn -ttjx-online.cn-hangzhou.log.aliyuncs.com -tu.baixing.com -tui.gtimg.com -tuiguang.meitu.com -tuijian.baidu.com -tunion-api.m.taobao.com -tvupgrade.yunos.com -tw13b093.sandai.net -tw.alimama.cn -twsina.allyes.com -txtad.jijiplayer.com -t.youmi.net -u0.s.minisplat.cn -u1.img.mobile.sina.cn -u1.s.minisplat.cn -u2.s.minisplat.cn -u.ads8.com -uat1.bfsspadserver.8le8le.com -ubmcmm.baidustatic.com -uc9.ucweb.com -ucstat.baidu.com -ucus.ucweb.com -udash.umengcloud.com -udc.msn.com -u.domob.cn -uedas.qdmm.com -uedas.qidian.com -uid.ksosoft.com -ulic.baidu.com -ulogs.umeng.com -um2.eqads.com -umeng.com -umid.orion.meizu.com -ums.adtechjp.com -ums.adtechus.com -um.simpli.fi -unconf.mobad.ijinshan.com -union2.50bang.org -union.baidu.com -union.dbba.cn -union.discuz.qq.com -unionimage.baidu.com -union.mop.com -union.sogou.com -unitacs.m.taobao.com -up1.tj.u2.ucweb.com -up4.ucweb.com -up.cm.ksmobile.com -update.avlyun.sec.miui.com -updatecenter.qq.com -upoll.umengcloud.com -ups.ksmobile.net -upush.res.meizu.com -us.adserver.yahoo.com -us.bannyat.com -user1.game.qq.com -userimg.qunar.com -us-u.openx.net -usync.aws.rubiconproject.com -utility.baidu.com -utk.baidu.com -utop.umengcloud.com -utp.ucweb.com -u.uc123.com -u.ucfly.com -uuidapi.yunos.com -uxip.meizu.com -v1-feed.idreamsky.com -v1.log.moji.com -v2.fm.n.duokanbox.com -v2.reachmax.cn -v.admaster.com.cn -va.gxpan.cn -vamaker.cm.admaster.com.cn -vatrack.hinet.net -vbaof.admaster.com.cn -vdapprecv.app.cntvwb.cn -v.domob.cn -v.gdt.qq.com -vhunantv.admaster.com.cn -video-ad-stats.googlesyndication.com -videopush.baidu.com -video.ureport.push.qq.com -video.wap.mpush.qq.com -video.ymapp.com -view.admaster.com.cn -viqiyi.admaster.com.cn -vjoz.lu.sogou.com -vletv.admaster.com.cn -vmzqwz.cn -vpic.video.qq.com -vps.inte.sogou.com -vqq.admaster.com.cn -vs19.gzcu.u3.ucweb.com -vs2.gzcu.u3.ucweb.com -vs7.gzcu.u3.ucweb.com -vs8.gzct.u3.ucweb.com -vs8.gzcu.u3.ucweb.com -vsohu.admaster.com.cn -vt.ipinyou.com -vv84.bj.bcebos.com -wa.gtimg.com -wan.2345.com -wangmeng.baidu.com -wanproxy.127.net -wan.sogou.com -wap.114so.cn -wap3.ucweb.com -wap.mpush.qq.com -wapwbclick.mobile.sina.cn -watson.microsoft.com -wbapp.mobile.sina.cn -wbapp.uve.weibo.com -wb.brand.sogou.com -wbclick.mobile.sina.cn -wb.gtimg.com -wbpctips.mobile.sina.cn -w.domob.cn -wds.inte.sogoucdn.com -web.sogou.com -webstat.kuwo.cn -web-track.go2yd.com -wenku-cms.bj.bcebos.com -weyyae.com -wgo.mmstat.com -widget.weibo.com -win.gdt.qq.com -wip3.adobe.com -wisemedia.cm.admaster.com.cn -wisepush.video.baidu.com -wl.51taifu.com -w.l.qq.com -wm.baidu.com -w.m.taobao.com -wn.pos.baidu.com -wo.iuni.com.cn -woocall.sina.com.cn -woodpecker.uc.cn -ws.ksmobile.net -ws.sj.qq.com -wtradv.market.xiaomi.com -wuliao.epro.sogou.com -wup.imtt.qq.com -wuqdebjfhjas.bid -wwis-dubc1-vip60.adobe.com -www.114so.cn -www.1680go.com -www.202m.com -www.706529.com -www.716703.com -www.adpush.cn -www.adsage.cn -www.adsmogo.com -www.adsmogo.net -www.ad-survey.com -www.adview.cn -www.adwo.com -www.allyes.com -www.chenggao.cn -www.count.51yes.com -www.doumob.com -www.fathionmall.com -www.flurry.com -www.googleadservices.com -www.gz00005.top -www.hao934.com -www.huaxinxunye.cn -www.i1236.net -www.ipinyou.com -www.ipinyou.com.cn -www.jiubuhua.com -www.minesage.com -www.ok365.com -www.openx.net -www.pro.cn -www.remote88.com -www.searchswapper.com -www.tz-dsp.com -www.umeng.com -www.uulucky.com -www.uyunad.com -www.whalecloud.com -www.wifijia.net -www.yueyelive.com -wx.xwjqr.com -w.ymapp.com -x.adpro.cn -x.cnxad.com -x.domob.cn -xf.yellowto.com -xiaomiir.yaokantv.com -xs.1drj.com -xs.he9630.com -xtruh.uranus.sogou.com -xunlei.adx.admaster.com.cn -xz-development.oss-cn-beijing.aliyuncs.com -yads.c.yimg.jp -yads.yahoo.co.jp -yam.adsbro.com -y.domob.cn -yee.js.cn -yellowto.com -yeskyafp.allyes.com -yiliao.hupan.com -ym.adnxs.com -youjia2016.com -youle.tom.com -ypv.chengadx.com -yt.mmstat.com -yun.lvehaisen.com -yun.tuia.cn -yun.yuyiya.com -yyffeicd.m.qxs.la -zbz.m.qxs.la -zc.adpush.cn -zc.biz.weibo.com -z.clickvip.shop -z.domob.cn -zhushou.2345.com -zhwnlapi.etouch.cn -zjvnet.allyes.com -znsv.baidu.com -zt.adsage.com -ztrpm.lu.sogou.com -zymo.mps.weibo.com -zz.bdstatic.com -zzy1.quyaoya.com \ No newline at end of file diff --git a/app/src/main/assets/iconfont.ttf b/app/src/main/assets/iconfont.ttf index c1a8aa0..9d1e7a5 100644 Binary files a/app/src/main/assets/iconfont.ttf and b/app/src/main/assets/iconfont.ttf differ diff --git a/app/src/main/assets/image/gif_player.png b/app/src/main/assets/image/gif_player.png new file mode 100644 index 0000000..fa0ba6e Binary files /dev/null and b/app/src/main/assets/image/gif_player.png differ diff --git a/app/src/main/assets/image/image_holder.png b/app/src/main/assets/image/image_holder.png new file mode 100644 index 0000000..dd7a027 Binary files /dev/null and b/app/src/main/assets/image/image_holder.png differ diff --git a/app/src/main/assets/image/image_holder_click_to_load.png b/app/src/main/assets/image/image_holder_click_to_load.png deleted file mode 100644 index 42504b1..0000000 Binary files a/app/src/main/assets/image/image_holder_click_to_load.png and /dev/null differ diff --git a/app/src/main/assets/image/image_holder_load_failed.png b/app/src/main/assets/image/image_holder_load_failed.png deleted file mode 100644 index fcc76f5..0000000 Binary files a/app/src/main/assets/image/image_holder_load_failed.png and /dev/null differ diff --git a/app/src/main/assets/image/image_holder_loading.png b/app/src/main/assets/image/image_holder_loading.png deleted file mode 100644 index cbabba5..0000000 Binary files a/app/src/main/assets/image/image_holder_loading.png and /dev/null differ diff --git a/app/src/main/assets/js/highlight.pack.js b/app/src/main/assets/js/highlight.pack.js new file mode 100644 index 0000000..df855d7 --- /dev/null +++ b/app/src/main/assets/js/highlight.pack.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],o=Object.keys,_={},g={},C=!0,n=/^(no-?highlight|plain|text)$/i,E=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function d(e){return e.nodeName.toLowerCase()}function R(e){return n.test(e)}function i(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function p(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),d(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function v(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function l(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return i(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[i(n,{starts:n.starts?i(n.starts):null})]:Object.isFrozen(n)?[i(n)]:[n]}function u(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(u)}}function M(n,t){var i={};return"string"==typeof n?r("keyword",n):o(n).forEach(function(e){r(e,n[e])}),i;function r(a,e){t&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n,t,r=e.split("|");i[r[0]]=[a,(n=r[0],(t=r[1])?Number(t):function(e){return-1!=c.indexOf(e.toLowerCase())}(n)?0:1)]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=new RegExp(n.toString()+"|").exec("").length-1+1}for(var r=0;r')+n+(t?"":m)}function o(){p+=(null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=s(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||o(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function b(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function s(e){var n,t,r,a,i,o,c,l,u,s,f=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=E.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=f?T(f,i,!0):w(i),(t=p(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=v(t,p(a),i)),r.value=b(r.value),e.innerHTML=r.value,e.className=(o=e.className,c=f,l=r.language,u=c?g[c]:l,s=[o.trim()],o.match(/\bhljs\b/)||s.push("hljs"),-1===o.indexOf(u)&&s.push(u),s.join(" ").trim()),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,s)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[g[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=b,a.highlightBlock=s,a.configure=function(e){B=i(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}u(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){g[e]=n})},a.listLanguages=function(){return o(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=i,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("swift",function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t=e.C("/\\*","\\*/",{c:["self"]}),n={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},r={cN:"string",c:[e.BE,n],v:[{b:/"""/,e:/"""/},{b:/"/,e:/"/}]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return n.c=[a],{k:i,c:[r,e.CLCM,t,{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,r,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{bK:"import",e:/$/,c:[e.CLCM,t]}]}});hljs.registerLanguage("ini",function(e){var b={cN:"number",relevance:0,v:[{b:/([\+\-]+)?[\d]+_[\d_]+/},{b:e.NR}]},a=e.C();a.v=[{b:/;/,e:/$/},{b:/#/,e:/$/}];var c={cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"literal",b:/\bon|off|true|false|yes|no\b/},n={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",relevance:10},{b:'"""',e:'"""',relevance:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[a,{cN:"section",b:/\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/,cN:"attr",starts:{e:/$/,c:[a,{b:/\[/,e:/\]/,c:[a,r,c,n,b,"self"],relevance:0},r,c,n,b]}}]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={b:/\{\{/,relevance:0},l={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,b],relevance:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,b],relevance:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,b,a,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,b,a,c]},{b:/(u|r|ur)'/,e:/'/,relevance:10},{b:/(u|r|ur)"/,e:/"/,relevance:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,a,c]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,a,c]},e.ASM,e.QSM]},n={cN:"number",relevance:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,n,l,e.HCM]};return c.c=[l,n,b],{aliases:["py","gyp","ipython"],k:r,i:/(<\/|->|\?)|=>/,c:[b,n,{bK:"if",relevance:0},l,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},r={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},n=[e.C("#","$",{c:[r]}),e.C("^\\=begin","^\\=end",{c:[r],relevance:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:b},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,rB:!0,c:[{b:/<<[-~]?'?/},{b:/\w+/,endSameAsBegin:!0,c:[e.BE,s]}]}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:b},l=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",relevance:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:c}],relevance:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:b},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),relevance:0}].concat(n);s.c=l;var d=[{b:/^\s*=>/,starts:{e:"$",c:i.c=l}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:b,i:/\/\*/,c:n.concat(d).concat(l)}});hljs.registerLanguage("yaml",function(e){var b="true false yes no null",a={cN:"string",relevance:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,{cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]}]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[{cN:"attr",v:[{b:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{b:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{b:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{cN:"meta",b:"^---s*$",relevance:10},{cN:"string",b:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,relevance:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"\\-(?=[ ]|$)",relevance:0},e.HCM,{bK:b,k:{literal:b}},{cN:"number",b:e.CNR+"\\b"},a]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",relevance:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]{0,3}(?=\\W)",relevance:0},{b:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",a={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,a]},{b:/[:\(,=]\s*/,relevance:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[a]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,relevance:0}])}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:r},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("cpp",function(e){function t(e){return"(?:"+e+")?"}var r="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",i=(t(a),t("<.*?>"),{cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"}),c={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[e.BE]},{b:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",e:"'",i:"."},{b:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},n={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},c:[{b:/\\\n/,relevance:0},e.inherit(c,{cN:"meta-string"}),{cN:"meta-string",b:/<.*?>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},o={cN:"title",b:t(a)+e.IR,relevance:0},l=t(a)+e.IR+"\\s*\\(",u={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},p=[i,e.CLCM,e.CBCM,s,c],m={v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:u,c:p.concat([{b:/\(/,e:/\)/,k:u,c:p.concat(["self"]),relevance:0}]),relevance:0},d={cN:"function",b:"((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+"+l,rB:!0,e:/[{;=]/,eE:!0,k:u,i:/[^\w\s\*&:<>]/,c:[{b:r,k:u,relevance:0},{b:l,rB:!0,c:[o],relevance:0},{cN:"params",b:/\(/,e:/\)/,k:u,relevance:0,c:[e.CLCM,e.CBCM,c,s,i,{b:/\(/,e:/\)/,k:u,relevance:0,c:["self",e.CLCM,e.CBCM,c,s,i]}]},i,e.CLCM,e.CBCM,n]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],k:u,i:"",k:u,c:["self",i]},{b:e.IR+"::",k:u},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},e.TM]}]),exports:{preprocessor:n,strings:c,k:u}}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,relevance:0}]},c=[e.BE,r,n],a=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:c,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",relevance:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",relevance:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",relevance:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",relevance:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",relevance:5},{b:"qw\\s+q",e:"q",relevance:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],relevance:0},{b:"-?\\w+\\s*\\=\\>",c:[],relevance:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",relevance:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],relevance:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,relevance:5,c:[e.TM]},{b:"-\\w\\b",relevance:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=a,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:s.c=a}});hljs.registerLanguage("scss",function(e){var t="@[a-z-]+",r={cN:"variable",b:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={cN:"number",b:"#[0-9A-Fa-f]+"};e.CSSNM,e.QSM,e.ASM,e.CBCM;return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",relevance:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",relevance:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{cN:"selector-pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"selector-pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},r,{cN:"attribute",b:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[r,i,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@(page|font-face)",l:t,k:"@page @font-face"},{b:"@",e:"[{;]",rB:!0,k:"and or not only",c:[{b:t,cN:"keyword"},r,e.QSM,e.ASM,i,e.CSSNM]}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:""},{cN:"attribute",b:/\w+/,relevance:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,relevance:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("typescript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},n={cN:"meta",b:"@"+r},a={b:"\\(",e:/\)/,k:t,c:["self",e.QSM,e.ASM,e.NM]},c={cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:[e.CLCM,e.CBCM,n,a]},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},b={cN:"string",b:"`",e:"`",c:[e.BE,o]};return o.c=[e.ASM,e.QSM,i,l,b,s,e.RM],{aliases:["ts"],k:t,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,i,l,b,e.CLCM,e.CBCM,s,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:["self",e.CLCM,e.CBCM]}]}]}],relevance:0},{cN:"function",bK:"function",e:/[\{;]/,eE:!0,k:t,c:["self",e.inherit(e.TM,{b:r}),c],i:/%/,relevance:0},{bK:"constructor",e:/[\{;]/,eE:!0,c:["self",c]},{b:/module\./,k:{built_in:"module"},relevance:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,relevance:0},n,a]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},a={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,relevance:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],relevance:0},e.HCM,a,{cN:"",b:/\\"/},{cN:"string",b:/'/,e:/'/},t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("go",function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:n,i:"",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],relevance:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],relevance:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("java",function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",t={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0};return{aliases:["jsp"],k:a,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{b:/\w+@/,relevance:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",relevance:0},{cN:"function",b:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:a,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:a,relevance:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},t,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[r],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},r]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",relevance:0},{cN:"bullet",b:"^\\s*([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",relevance:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```\\w*\\s*$",e:"^```[ ]*$"},{b:"`.+?`"},{b:"^( {4}|\\t)",e:"$",relevance:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,relevance:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],relevance:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("less",function(e){function r(e){return{cN:"string",b:"~?"+e+".*?"+e}}function t(e,r,t){return{cN:e,b:r,relevance:t}}var a="[\\w-]+",c="("+a+"|@{"+a+"})",s=[],n=[],b={b:"\\(",e:"\\)",c:n,relevance:0};n.push(e.CLCM,e.CBCM,r("'"),r('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},t("number","#[0-9A-Fa-f]+\\b"),b,t("variable","@@?"+a,10),t("variable","@{"+a+"}"),t("built_in","~?`[^`]*?`"),{cN:"attribute",b:a+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var i=n.concat({b:"{",e:"}",c:s}),l={bK:"when",eW:!0,c:[{bK:"and not"}].concat(n)},o={b:c+"\\s*:",rB:!0,e:"[;}]",relevance:0,c:[{cN:"attribute",b:c,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",relevance:0,c:n}}]},u={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:n,relevance:0}},v={cN:"variable",v:[{b:"@"+a+"\\s*:",relevance:15},{b:"@"+a}],starts:{e:"[;}]",rE:!0,c:i}},C={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:c,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",relevance:0,c:[e.CLCM,e.CBCM,l,t("keyword","all\\b"),t("variable","@{"+a+"}"),t("selector-tag",c+"%?",0),t("selector-id","#"+c),t("selector-class","\\."+c,0),t("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:i},{b:"!important"}]};return s.push(e.CLCM,e.CBCM,u,v,o,C),{cI:!0,i:"[=>'/<($\"]",c:s}});hljs.registerLanguage("properties",function(e){var r="[ \\t\\f]*",t="("+r+"[:=]"+r+"|[ \\t\\f]+)",n="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",c={e:t,relevance:0,starts:{cN:"string",e:/$/,relevance:0,c:[{b:"\\\\\\n"}]}};return{cI:!0,i:/\S/,c:[e.C("^\\s*[!#]","$"),{b:n+t,rB:!0,c:[{cN:"attr",b:n,endsParent:!0,relevance:0}],starts:c},{b:a+t,rB:!0,relevance:0,c:[{cN:"meta",b:a,endsParent:!0,relevance:0}],starts:c},{cN:"attr",relevance:0,b:a+r+"$"}]}});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",n={b:t,e:a,c:["self"]},l=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[n],relevance:10})];return{l:e.UIR,k:{literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstringmodule next pairs pcall print rawequal rawget rawset require select setfenvsetmetatable tonumber tostring type unpack xpcall arg selfcoroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},c:l.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:l}].concat(l)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[n],relevance:5}])}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php","php3","php4","php5","php6","php7"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%)?(\\[\\])?";return{aliases:["csharp","c#"],k:a,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",relevance:0},{b:"\x3c!--|--\x3e"},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},o,i,{bK:"class interface",e:/[{;=]/,i:/[^\s:,]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",relevance:0},{cN:"function",b:"("+d+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/\s*[{;=]/,eE:!0,k:a,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],relevance:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:a,relevance:0,c:[o,i,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("kotlin",function(e){var t={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={cN:"symbol",b:e.UIR+"@"},n={cN:"subst",b:"\\${",e:"}",c:[e.CNM]},c={cN:"variable",b:"\\$"+e.UIR},r={cN:"string",v:[{b:'"""',e:'"""(?=[^"])',c:[c,n]},{b:"'",e:"'",i:/\n/,c:[e.BE]},{b:'"',e:'"',i:/\n/,c:[e.BE,c,n]}]};n.c.push(r);var i={cN:"meta",b:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UIR+")?"},l={cN:"meta",b:"@"+e.UIR,c:[{b:/\(/,e:/\)/,c:[e.inherit(r,{cN:"meta-string"})]}]},s={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},b=e.C("/\\*","\\*/",{c:[e.CBCM]}),o={v:[{cN:"type",b:e.UIR},{b:/\(/,e:/\)/,c:[]}]},d=o;return d.v[1].c=[o],o.v[1].c=[d],{aliases:["kt"],k:t,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,b,{cN:"keyword",b:/\b(break|continue|return|this)\b/,starts:{c:[{cN:"symbol",b:/@\w+/}]}},a,i,l,{cN:"function",bK:"fun",e:"[(]|$",rB:!0,eE:!0,k:t,i:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"type",b://,k:"reified",relevance:0},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,relevance:0,c:[{b:/:/,e:/[=,\/]/,eW:!0,c:[o,e.CLCM,b],relevance:0},e.CLCM,b,i,l,r,e.CNM]},b]},{cN:"class",bK:"class interface trait",e:/[:\{(]|$/,eE:!0,i:"extends implements",c:[{bK:"public protected internal private constructor"},e.UTM,{cN:"type",b://,eB:!0,eE:!0,relevance:0},{cN:"type",b:/[,:]\s*/,e:/[<\(,]|$/,eB:!0,rE:!0},i,l]},r,{cN:"meta",b:"^#!/usr/bin/env",e:"$",i:"\n"},s]}});hljs.registerLanguage("plaintext",function(e){return{disableAutodetect:!0}});hljs.registerLanguage("javascript",function(e){var r="<>",a="",t={b:/<[A-Za-z0-9\\._:-]+/,e:/\/[A-Za-z0-9\\._:-]+>|\/>/},c="[A-Za-z$_][0-9A-Za-z$_]*",n={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:n,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},b={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},l={cN:"string",b:"`",e:"`",c:[e.BE,o]};o.c=[e.ASM,e.QSM,i,b,l,s,e.RM];var u=o.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx","mjs","cjs"],k:n,c:[{cN:"meta",relevance:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,i,b,l,e.CLCM,e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+",c:[{cN:"type",b:"\\{",e:"\\}",relevance:0},{cN:"variable",b:c+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{b:/(?=[^\n])\s/,relevance:0}]}]}),e.CBCM,s,{b:/[{,\n]\s*/,relevance:0,c:[{b:c+"\\s*:",rB:!0,relevance:0,c:[{cN:"attr",b:c,relevance:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+c+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:c},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:n,c:u}]}]},{cN:"",b:/\s/,e:/\s*/,skip:!0},{v:[{b:r,e:a},{b:t.b,e:t.e}],sL:"xml",c:[{b:t.b,e:t.e,skip:!0,c:["self"]}]}],relevance:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:c}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:u}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor get set",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.CLCM,e.CBCM],c=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:c,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})].concat(n),i:"\\S"},a={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return c.push(t,a),n.forEach(function(e){c.push(e)}),{c:c,k:i,i:"\\S"}});hljs.registerLanguage("css",function(e){var c={b:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM,e.CSSNM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$",c:[e.ASM,e.QSM]},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(page|font-face)",l:"@[a-z-]+",k:"@page @font-face"},{b:"@",e:"[{;]",i:/:/,rB:!0,c:[{cN:"keyword",b:/@\-?\w[\w]*(\-\w+)*/},{b:/\s/,eW:!0,eE:!0,relevance:0,k:"and or not only",c:[{b:/[a-z-]+:/,cN:"attribute"},e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,c]}]}});hljs.registerLanguage("objectivec",function(e){var t=/[a-zA-Z@][a-zA-Z0-9_]*/,i="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:{keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},l:t,i:"/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},{cN:"class",b:"("+i.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:i,l:t,c:[e.UTM]},{b:"\\."+e.UIR,relevance:0}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},c:[{cN:"string",b:"'",e:"'",c:[{b:"''"}]},{cN:"string",b:'"',e:'"',c:[{b:'""'}]},{cN:"string",b:"`",e:"`"},e.CNM,e.CBCM,t,e.HCM]},e.CBCM,t,e.HCM]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",relevance:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",relevance:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/^\*{15}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}}); \ No newline at end of file diff --git a/app/src/main/assets/js/lazyload.js b/app/src/main/assets/js/lazyload.js index 5e5e1c8..f7a3263 100644 --- a/app/src/main/assets/js/lazyload.js +++ b/app/src/main/assets/js/lazyload.js @@ -10,7 +10,6 @@ * Copyright 2013 Luís Almeida * https://github.com/luis-almeida */ - ;(function($) { $.fn.unveil = function(threshold, callback) { var $w = $(window), diff --git a/app/src/main/assets/js/media.js b/app/src/main/assets/js/media.js index ae4f617..ad103b4 100644 --- a/app/src/main/assets/js/media.js +++ b/app/src/main/assets/js/media.js @@ -1,169 +1,208 @@ /* * 设置图片的默认加载行为 - * - * native 需要实现的接口有: - * String readImageCache(String url); - * boolean isAutoLoadImage(); - * void loadImage(String url); - * void openImage(String urls, int index); - * - * native 可以调用的方法有: - * void onImageLoadFailed(String url); - * void onImageLoadSuccess(String url, String localUrl); */ -var IMAGE_HOLDER_CLICK_TO_LOAD_URL = 'file:///android_asset/image/image_holder_click_to_load.png'; -var IMAGE_HOLDER_LOAD_FAILED_URL = 'file:///android_asset/image/image_holder_load_failed.png'; -var IMAGE_HOLDER_LOADING_URL = 'file:///android_asset/image/image_holder_loading.png'; - -var loaded = false; - - -var articleId = $('article').attr('id'); -//ImageBridge.log("==============================================media文件被执行" + articleId ); +// ArticleBridge.log("触发脚本" ); // 在这里调用是因为在第一次打开ArticleActivity时,渲染WebView的内容比较慢,此时在ArticleActivity中调用setupImage不会执行。 -// 所以等WebView加载到这个文件时,尝试再去执行tryInitJs,当此页面等于当前展示的文章时,就执行setupImage。 -// 之所以不能直接就执行setupImage,因为在viewpager中预加载而生成webview的时候,这里的懒加载就被触发了,3个webview首屏的图片就都被触发下载了 -//setTimeout( ImageBridge.tryInitJs(articleId),100 ); -setTimeout( setupImage(),10 ); +// 不直接执行setupImage是因为在viewpager中预加载而生成webview的时候,这里的懒加载就被触发了,3个webview首屏的图片就都被触发下载了 +setTimeout( optimize(),10 ); + +function optimize() { + handleImage(); + handleQQVideoUrl(); + handleVideo(); + handleIFrame(); + handleEmbed(); + handleAudio(); + handleTable(); +} -function setupImage() { -// ImageBridge.log("============================================== 准备执行 setupImage" ); - if(loaded){ - return; - } -// ImageBridge.log("============================================== 开始执行 setupImage" ); - var index = 0; - articleId = document.getElementsByTagName('article')[0].id; -// ImageBridge.log("===加载图片" + articleId ); + +function handleImage() { + var articleId = $('article').attr('id'); $('img').each(function() { var image = $(this); var originalUrl = image.attr('original-src'); + if( originalUrl == null || originalUrl == "" || originalUrl == undefined ){ + return true; + } var url = image.attr('src'); - image.attr('index', index ++); + // 为什么用 hashCode 作为图片的 id 来传递,而不是 src, window.btoa(url)? + // 1、这里获得的src是经过转义的,而传递到java层再传回来的src是未经过转义的(特别是中文)。 + // 2、window.btoa(url) 中 url 的字符不能超出 0x00~0xFF 范围(不能有中文或特殊字符),否则会有无效字符异常。 + image.attr('id', hashCode(originalUrl) ); + image.unveil(200, function() { - var index = parseInt($(this).attr('index')); - ImageBridge.loadImage(articleId,index, url, originalUrl); - }); + var image = $(this); + if(!image.hasClass("image-holder")){ + image.addClass("image-holder"); + //ArticleBridge.log("触发脚本 + 加载" + articleId + " , " + image.attr('referrerpolicy') ); + ArticleBridge.readImage(articleId, image.attr('id'), originalUrl); + } + }); }); - $('img').click(function(event) { var image = $(this); var displayUrl = image.attr('src'); var originalUrl = image.attr('original-src'); // 此时去下载图片 - if (displayUrl == IMAGE_HOLDER_CLICK_TO_LOAD_URL || displayUrl == IMAGE_HOLDER_LOAD_FAILED_URL) { + if (displayUrl == IMAGE_HOLDER_CLICK_TO_LOAD_URL) { + image.attr('src', IMAGE_HOLDER_LOADING_URL); + ArticleBridge.downImage(articleId, image.attr('id'), originalUrl, false); + }else if (displayUrl == IMAGE_HOLDER_LOAD_FAILED_URL){ image.attr('src', IMAGE_HOLDER_LOADING_URL); - var index = parseInt($(this).attr('index')); - ImageBridge.downImage(articleId,index, originalUrl); + ArticleBridge.downImage(articleId, image.attr('id'), originalUrl, false); + }else if (displayUrl == IMAGE_HOLDER_IMAGE_ERROR_URL){ + image.attr('src', IMAGE_HOLDER_LOADING_URL); + ArticleBridge.downImage(articleId, image.attr('id'), originalUrl, true); }else if (displayUrl != IMAGE_HOLDER_LOADING_URL){ // 由于此时正在加载中所以不处理 - var index = 0; - $('img').each(function() { - if (event.target === this) { - index = parseInt($(this).attr('index')); - } - }); - ImageBridge.openImage(articleId, displayUrl , index); + ArticleBridge.openImage(articleId, displayUrl); } - - // 阻止元素发生默认的行为(例如,当点击提交按钮时阻止对表单的提交)。 + // 阻止元素发生默认的行为(例如点击提交按钮时阻止对表单的提交) event.preventDefault(); - // 该方法将停止事件的传播,阻止它被分派到其他 Document 节点。在事件传播的任何阶段都可以调用它。注意,虽然该方法不能阻止同一个 Document 节点上的其他事件句柄被调用,但是它可以阻止把事件分派到其他节点。 + // 停止事件传播,阻止它被分派到其他 Document 节点。在事件传播的任何阶段都可以调用它。 + // 注意,虽然该方法不能阻止同一个 Document 节点上的其他事件句柄被调用,但是它可以阻止把事件分派到其他节点。 event.stopPropagation(); }); - - handleIframe(); - handleEmbed(); - loaded = true; } +// 将老的QQ视频链接换成新的 +function handleQQVideoUrl() { + var list = document.querySelectorAll('iframe[src^="http://v.qq.com/iframe/player.html"],iframe[src^="https://v.qq.com/iframe/player.html"]'); + for (var i = 0,len = list.length; i < len; i++) { + list[i].src = list[i].src.replace('v.qq.com/iframe/player.html', 'v.qq.com/txp/iframe/player.html'); + } +} // 针对 iframe 标签做处理 -function handleIframe(){ - // 初始化 iframe 的点击事件 - var iframes = document.getElementsByTagName('iframe'); - for (var i = 0, l = iframes.length; i < l; i++) { - var iframe = iframes[i]; - if( hasAudio(iframe.src) ){ - continue; - } - wrapFrame( iframe ); - }; +function handleIFrame(){ + $('iframe').each(function() { + var frame = $(this); + frame.removeAttr("sandbox");// sandbox 会限制 iframe 的各种能力 + frame.attr("frameborder", "0"); + frame.attr("allowfullscreen", ""); + frame.attr("scrolling", "no"); + frame.attr("src", frame.attr("src").replace(/(width|height)=\d+/ig, "").replace(/(&(amp;)*){2,}/ig, "&")); + // 让iframe默认为点击新窗口打开 + frame.attr("style", "pointer-events:none;"); + frame.wrap('
'); + frame.parent().click(function(event) { + ArticleBridge.openLink(frame.attr("src")); + event.preventDefault(); + }); + // 当iframe加载完毕后,根据src来判断是否需要关闭新窗口打开 + frame.on('load', function() { + if( loadOnInner(frame.attr('src')) ){ + $(this).attr("style", "pointer-events:auto;"); + } + }); + }); } - -// 针对 embed 标签做处理 function handleEmbed(){ - // 初始化 embed 的点击事件 - var embeds = document.getElementsByTagName('embed'); - for (var i = 0, l = embeds.length; i < l; i++) { - var embed = embeds[i]; - wrapFrame( embed ); - }; + $('embed').each(function() { + var frame = $(this); + frame.attr("autostart","1"); + frame.attr("src", frame.attr("src").replace(/(width|height)=\d+/ig, "").replace(/(&(amp;)*){2,}/ig, "&")); + frame.attr("style", "pointer-events:none;"); + frame.wrap('
'); + frame.parent().click(function(event) { + ArticleBridge.openLink(frame.attr("src")); + event.preventDefault(); + }); + // 当iframe加载完毕后,根据src来判断是否需要关闭新窗口打开 + frame.on('load', function() { + if( loadOnInner(frame.attr('src')) ){ + $(this).attr("style", "pointer-events:auto;"); + } + }); + }); } - - -function wrapFrame( frame ){ - a = document.createElement('a'); - a.setAttribute('href', "javascript:void(0)"); - (function( src ){ - a.onclick = function () { - ImageBridge.openLink( src ); - } - })( frame.src ) - d = document.createElement('div'); - d.style.width = '100%'; - d.style.height = frame.offsetHeight + 'px'; - d.style.top = frame.offsetTop + 'px'; - d.style.left = frame.offsetLeft + 'px'; - d.style.position = 'absolute'; - d.style.zIndex='2147483647'; - d.style.overflow='hidden'; - d.innerHTML = ""; - d.style.lineHeight = frame.offsetHeight + 'px'; - d.style.textAlign = 'center'; - a.appendChild(d); - frame.offsetParent.appendChild(a); +function handleAudio(){ + $('audio').each(function() { + var audio = $(this); + audio.attr("controls", "true"); + audio.attr("width", "100%") + audio.attr("style", "pointer-events:none;"); + audio.wrap('
'); + audio.parent().click(function(event) { + ArticleBridge.openAudio( audio.attr("src") ); + event.preventDefault(); + }); + }); +} +function handleVideo(){ + $('video').each(function() { + var video = $(this); + video.attr("controls", "true"); + video.attr("width", "100%"); + video.attr("height", "auto"); + video.attr("preload", "metadata"); + video.wrap('
'); + }); +} +function handleTable(){ + $('table').each(function() { + $(this).wrap('
'); + }); } -/* - d.style.width = frame.offsetWidth + 'px'; - d.style.opacity = '0'; - d.style.filter = 'alpha(opacity=0)'; - d.style.background = 'black'; -*/ - -function hasAudio(url){ - if ( url.indexOf("music.163.com/outchain/player") != -1 ) { - return true; - } - return false; +function loadOnInner(url){ + var flags = ["music.163.com/outchain/player","player.bilibili.com/player.html","bilibili.com/blackboard/html5mobileplayer.html","player.youku.com","youtube.com/embed","open.iqiyi.com","v.qq.com","letv.com","sohu.com","fpie1.com/#/video","fpie2.com/#/video","www.google.com/maps/embed"]; + for (var i = 0; i < flags.length; i++) { + if (url.indexOf(flags[i]) != -1 ){ + return true; + } + } + return false; } +function findImageById(imgId) { + return $('img[id="' + imgId + '"]'); +} +function onImageLoadNeedClick(imgId) { + var image = findImageById(imgId); + if (image) { + image.attr('src', IMAGE_HOLDER_CLICK_TO_LOAD_URL); + } -function findImageByUrl(url) { - return $('img[original-src="' + url + '"]'); } -function onImageLoadNeedClick(url) { - var image = findImageByUrl(url); +function onImageLoading(imgId) { + var image = findImageById(imgId); if (image) { - image.attr('src', IMAGE_HOLDER_CLICK_TO_LOAD_URL); + image.attr('src', IMAGE_HOLDER_LOADING_URL); } } - -function onImageLoadFailed(url) { - var image = findImageByUrl(url); +function onImageLoadFailed(imgId) { + var image = findImageById(imgId); if (image) { image.attr('src', IMAGE_HOLDER_LOAD_FAILED_URL); } } - -function onImageLoadSuccess(url, displayUrl) { - var image = findImageByUrl(url); +function onImageError(imgId) { + var image = findImageById(imgId); if (image) { - image.removeClass('image-holder'); - image.attr('src', displayUrl); + image.attr('src', IMAGE_HOLDER_IMAGE_ERROR_URL); + } +} +function onImageLoadSuccess(imgId, displayUrl) { + var image = findImageById(imgId); + image.attr('src', displayUrl); +} + +//产生一个hash值,只有数字,规则和java的hashcode规则相同 +function hashCode(str){ + var h = 0; + var len = str.length; + for(var i = 0; i < len; i++){ + var tmp=str.charCodeAt(i); + h = 31 * h + tmp; + if(h>0x7fffffff || h<0x80000000){ + h=h & 0xffffffff; + } } -} \ No newline at end of file + // 之所以用字符串格式,是因为通过$(this).attr('id')获取到的是字符串格式。 + return (h).toString(); +}; diff --git a/app/src/main/assets/js/placeholder.min.js b/app/src/main/assets/js/placeholder.min.js new file mode 100644 index 0000000..2a4d159 --- /dev/null +++ b/app/src/main/assets/js/placeholder.min.js @@ -0,0 +1,2 @@ +/*! placeholder.js v3.1.0(修改默认的宽度,背景色,字色) | MIT License | https://github.com/hustcc/placeholder.js */ +!function(t,e){"object"==typeof module&&module.exports?module.exports=e(t):t.placeholder=e(t)}("undefined"!=typeof window?window:this,function(){function t(t){c&&u||(c=document.createElement("canvas"),u=c.getContext("2d"));var e=parseInt(t.a[0]),n=parseInt(t.a[1]);c.width=e,c.height=n,u.clearRect(0,0,e,n),u.fillStyle=t.c,u.fillRect(0,0,e,n),u.fillStyle=t.d,u.font=t.e+" normal "+t.f+" "+(t.g||100)+"px "+t.h;var r=1;if(""===t.g){var o=.7*e,l=.7*n,i=u.measureText(t.b).width,a=100;r=Math.min(o/i,l/a)}return u.translate(e/2,n/2),u.scale(r,r),u.textAlign="center",u.textBaseline="middle",u.fillText(t.b,0,0),c}function e(){return"#"+("00000"+(16777216*Math.random()<<0).toString(16)).slice(-6)}function n(t){t=t||{};var n=t.size||document.body.offsetWidth + 'x' + (document.body.offsetWidth*0.3),r=t.text||n,o=t.bgcolor||'#e0e0e0',l=t.color||'#868686',i=t.fstyle||"normal",a=t.fweight||"normal",c=t.fsize||"22",u=t.ffamily||"consolas",f={};return n=n.split("x"),2!==n.length&&(n=[128,128]),f.a=n,f.b=r,f.c=o,f.d=l,f.e=i,f.f=a,f.g=c,f.h=u,t=null,f}function r(e){return e=n(e),t(e)}function o(t){return r(t).toDataURL()}function l(t,e,n){return t.getAttribute(e)||n}function i(t){var e,n={},r=t.split("&");for(var o in r){e=r[o].split("=");try{n[e[0]]=decodeURIComponent(e[1])}catch(l){n[e[0]]=e[1]}}return n}function a(t){for(var e,n,r=document.querySelectorAll("img.placeholder"),a=0;a0?n.fn.concat.apply([],t):t}function F(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function q(t){return t in f?f[t]:f[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function H(t,e){return"number"!=typeof e||c[F(t)]?e:e+"px"}function I(t){var e,n;return u[t]||(e=a.createElement(t),a.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),u[t]=n),u[t]}function V(t){return"children"in t?o.call(t.children):n.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function B(n,i,r){for(e in i)r&&(R(i[e])||A(i[e]))?(R(i[e])&&!R(n[e])&&(n[e]={}),A(i[e])&&!A(n[e])&&(n[e]=[]),B(n[e],i[e],r)):i[e]!==t&&(n[e]=i[e])}function U(t,e){return null==e?n(t):n(t).filter(e)}function J(t,e,n,i){return Z(e)?e.call(t,n,i):e}function X(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function W(e,n){var i=e.className,r=i&&i.baseVal!==t;return n===t?r?i.baseVal:i:void(r?i.baseVal=n:e.className=n)}function Y(t){var e;try{return t?"true"==t||("false"==t?!1:"null"==t?null:/^0/.test(t)||isNaN(e=Number(t))?/^[\[\{]/.test(t)?n.parseJSON(t):t:e):t}catch(i){return t}}function G(t,e){e(t);for(var n=0,i=t.childNodes.length;i>n;n++)G(t.childNodes[n],e)}var t,e,n,i,C,N,r=[],o=r.slice,s=r.filter,a=window.document,u={},f={},c={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},l=/^\s*<(\w+|!)[^>]*>/,h=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,p=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,d=/^(?:body|html)$/i,m=/([A-Z])/g,g=["val","css","html","text","data","width","height","offset"],v=["after","prepend","before","append"],y=a.createElement("table"),x=a.createElement("tr"),b={tr:a.createElement("tbody"),tbody:y,thead:y,tfoot:y,td:x,th:x,"*":a.createElement("div")},w=/complete|loaded|interactive/,E=/^[\w-]*$/,j={},S=j.toString,T={},O=a.createElement("div"),P={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},A=Array.isArray||function(t){return t instanceof Array};return T.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var i,r=t.parentNode,o=!r;return o&&(r=O).appendChild(t),i=~T.qsa(r,e).indexOf(t),o&&O.removeChild(t),i},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return s.call(t,function(e,n){return t.indexOf(e)==n})},T.fragment=function(e,i,r){var s,u,f;return h.test(e)&&(s=n(a.createElement(RegExp.$1))),s||(e.replace&&(e=e.replace(p,"<$1>")),i===t&&(i=l.test(e)&&RegExp.$1),i in b||(i="*"),f=b[i],f.innerHTML=""+e,s=n.each(o.call(f.childNodes),function(){f.removeChild(this)})),R(r)&&(u=n(s),n.each(r,function(t,e){g.indexOf(t)>-1?u[t](e):u.attr(t,e)})),s},T.Z=function(t,e){return t=t||[],t.__proto__=n.fn,t.selector=e||"",t},T.isZ=function(t){return t instanceof T.Z},T.init=function(e,i){var r;if(!e)return T.Z();if("string"==typeof e)if(e=e.trim(),"<"==e[0]&&l.test(e))r=T.fragment(e,RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}else{if(Z(e))return n(a).ready(e);if(T.isZ(e))return e;if(A(e))r=k(e);else if(D(e))r=[e],e=null;else if(l.test(e))r=T.fragment(e.trim(),RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}}return T.Z(r,e)},n=function(t,e){return T.init(t,e)},n.extend=function(t){var e,n=o.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){B(t,n,e)}),t},T.qsa=function(t,e){var n,i="#"==e[0],r=!i&&"."==e[0],s=i||r?e.slice(1):e,a=E.test(s);return _(t)&&a&&i?(n=t.getElementById(s))?[n]:[]:1!==t.nodeType&&9!==t.nodeType?[]:o.call(a&&!i?r?t.getElementsByClassName(s):t.getElementsByTagName(e):t.querySelectorAll(e))},n.contains=a.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},n.type=L,n.isFunction=Z,n.isWindow=$,n.isArray=A,n.isPlainObject=R,n.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},n.inArray=function(t,e,n){return r.indexOf.call(e,t,n)},n.camelCase=C,n.trim=function(t){return null==t?"":String.prototype.trim.call(t)},n.uuid=0,n.support={},n.expr={},n.map=function(t,e){var n,r,o,i=[];if(M(t))for(r=0;r=0?e:e+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return r.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return Z(t)?this.not(this.not(t)):n(s.call(this,function(e){return T.matches(e,t)}))},add:function(t,e){return n(N(this.concat(n(t,e))))},is:function(t){return this.length>0&&T.matches(this[0],t)},not:function(e){var i=[];if(Z(e)&&e.call!==t)this.each(function(t){e.call(this,t)||i.push(this)});else{var r="string"==typeof e?this.filter(e):M(e)&&Z(e.item)?o.call(e):n(e);this.forEach(function(t){r.indexOf(t)<0&&i.push(t)})}return n(i)},has:function(t){return this.filter(function(){return D(t)?n.contains(this,t):n(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!D(t)?t:n(t)},last:function(){var t=this[this.length-1];return t&&!D(t)?t:n(t)},find:function(t){var e,i=this;return e=t?"object"==typeof t?n(t).filter(function(){var t=this;return r.some.call(i,function(e){return n.contains(e,t)})}):1==this.length?n(T.qsa(this[0],t)):this.map(function(){return T.qsa(this,t)}):[]},closest:function(t,e){var i=this[0],r=!1;for("object"==typeof t&&(r=n(t));i&&!(r?r.indexOf(i)>=0:T.matches(i,t));)i=i!==e&&!_(i)&&i.parentNode;return n(i)},parents:function(t){for(var e=[],i=this;i.length>0;)i=n.map(i,function(t){return(t=t.parentNode)&&!_(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return U(e,t)},parent:function(t){return U(N(this.pluck("parentNode")),t)},children:function(t){return U(this.map(function(){return V(this)}),t)},contents:function(){return this.map(function(){return o.call(this.childNodes)})},siblings:function(t){return U(this.map(function(t,e){return s.call(V(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return n.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=I(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=Z(t);if(this[0]&&!e)var i=n(t).get(0),r=i.parentNode||this.length>1;return this.each(function(o){n(this).wrapAll(e?t.call(this,o):r?i.cloneNode(!0):i)})},wrapAll:function(t){if(this[0]){n(this[0]).before(t=n(t));for(var e;(e=t.children()).length;)t=e.first();n(t).append(this)}return this},wrapInner:function(t){var e=Z(t);return this.each(function(i){var r=n(this),o=r.contents(),s=e?t.call(this,i):t;o.length?o.wrapAll(s):r.append(s)})},unwrap:function(){return this.parent().each(function(){n(this).replaceWith(n(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(e){return this.each(function(){var i=n(this);(e===t?"none"==i.css("display"):e)?i.show():i.hide()})},prev:function(t){return n(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return n(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var i=this.innerHTML;n(this).empty().append(J(this,t,e,i))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=J(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this[0].textContent:null},attr:function(n,i){var r;return"string"!=typeof n||1 in arguments?this.each(function(t){if(1===this.nodeType)if(D(n))for(e in n)X(this,e,n[e]);else X(this,n,J(this,i,t,this.getAttribute(n)))}):this.length&&1===this[0].nodeType?!(r=this[0].getAttribute(n))&&n in this[0]?this[0][n]:r:t},removeAttr:function(t){return this.each(function(){1===this.nodeType&&X(this,t)})},prop:function(t,e){return t=P[t]||t,1 in arguments?this.each(function(n){this[t]=J(this,e,n,this[t])}):this[0]&&this[0][t]},data:function(e,n){var i="data-"+e.replace(m,"-$1").toLowerCase(),r=1 in arguments?this.attr(i,n):this.attr(i);return null!==r?Y(r):t},val:function(t){return 0 in arguments?this.each(function(e){this.value=J(this,t,e,this.value)}):this[0]&&(this[0].multiple?n(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var i=n(this),r=J(this,t,e,i.offset()),o=i.offsetParent().offset(),s={top:r.top-o.top,left:r.left-o.left};"static"==i.css("position")&&(s.position="relative"),i.css(s)});if(!this.length)return null;var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(t,i){if(arguments.length<2){var r=this[0],o=getComputedStyle(r,"");if(!r)return;if("string"==typeof t)return r.style[C(t)]||o.getPropertyValue(t);if(A(t)){var s={};return n.each(A(t)?t:[t],function(t,e){s[e]=r.style[C(e)]||o.getPropertyValue(e)}),s}}var a="";if("string"==L(t))i||0===i?a=F(t)+":"+H(t,i):this.each(function(){this.style.removeProperty(F(t))});else for(e in t)t[e]||0===t[e]?a+=F(e)+":"+H(e,t[e])+";":this.each(function(){this.style.removeProperty(F(e))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(n(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?r.some.call(this,function(t){return this.test(W(t))},q(t)):!1},addClass:function(t){return t?this.each(function(e){i=[];var r=W(this),o=J(this,t,e,r);o.split(/\s+/g).forEach(function(t){n(this).hasClass(t)||i.push(t)},this),i.length&&W(this,r+(r?" ":"")+i.join(" "))}):this},removeClass:function(e){return this.each(function(n){return e===t?W(this,""):(i=W(this),J(this,e,n,i).split(/\s+/g).forEach(function(t){i=i.replace(q(t)," ")}),void W(this,i.trim()))})},toggleClass:function(e,i){return e?this.each(function(r){var o=n(this),s=J(this,e,r,W(this));s.split(/\s+/g).forEach(function(e){(i===t?!o.hasClass(e):i)?o.addClass(e):o.removeClass(e)})}):this},scrollTop:function(e){if(this.length){var n="scrollTop"in this[0];return e===t?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=e}:function(){this.scrollTo(this.scrollX,e)})}},scrollLeft:function(e){if(this.length){var n="scrollLeft"in this[0];return e===t?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=e}:function(){this.scrollTo(e,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),i=this.offset(),r=d.test(e[0].nodeName)?{top:0,left:0}:e.offset();return i.top-=parseFloat(n(t).css("margin-top"))||0,i.left-=parseFloat(n(t).css("margin-left"))||0,r.top+=parseFloat(n(e[0]).css("border-top-width"))||0,r.left+=parseFloat(n(e[0]).css("border-left-width"))||0,{top:i.top-r.top,left:i.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||a.body;t&&!d.test(t.nodeName)&&"static"==n(t).css("position");)t=t.offsetParent;return t})}},n.fn.detach=n.fn.remove,["width","height"].forEach(function(e){var i=e.replace(/./,function(t){return t[0].toUpperCase()});n.fn[e]=function(r){var o,s=this[0];return r===t?$(s)?s["inner"+i]:_(s)?s.documentElement["scroll"+i]:(o=this.offset())&&o[e]:this.each(function(t){s=n(this),s.css(e,J(this,r,t,s[e]()))})}}),v.forEach(function(t,e){var i=e%2;n.fn[t]=function(){var t,o,r=n.map(arguments,function(e){return t=L(e),"object"==t||"array"==t||null==e?e:T.fragment(e)}),s=this.length>1;return r.length<1?this:this.each(function(t,u){o=i?u:u.parentNode,u=0==e?u.nextSibling:1==e?u.firstChild:2==e?u:null;var f=n.contains(a.documentElement,o);r.forEach(function(t){if(s)t=t.cloneNode(!0);else if(!o)return n(t).remove();o.insertBefore(t,u),f&&G(t,function(t){null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src||window.eval.call(window,t.innerHTML)})})})},n.fn[i?t+"To":"insert"+(e?"Before":"After")]=function(e){return n(e)[t](this),this}}),T.Z.prototype=n.fn,T.uniq=N,T.deserializeValue=Y,n.zepto=T,n}();window.Zepto=Zepto,void 0===window.$&&(window.$=Zepto),function(t){function l(t){return t._zid||(t._zid=e++)}function h(t,e,n,i){if(e=p(e),e.ns)var r=d(e.ns);return(s[l(t)]||[]).filter(function(t){return!(!t||e.e&&t.e!=e.e||e.ns&&!r.test(t.ns)||n&&l(t.fn)!==l(n)||i&&t.sel!=i)})}function p(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function d(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function m(t,e){return t.del&&!u&&t.e in f||!!e}function g(t){return c[t]||u&&f[t]||t}function v(e,i,r,o,a,u,f){var h=l(e),d=s[h]||(s[h]=[]);i.split(/\s/).forEach(function(i){if("ready"==i)return t(document).ready(r);var s=p(i);s.fn=r,s.sel=a,s.e in c&&(r=function(e){var n=e.relatedTarget;return!n||n!==this&&!t.contains(this,n)?s.fn.apply(this,arguments):void 0}),s.del=u;var l=u||r;s.proxy=function(t){if(t=j(t),!t.isImmediatePropagationStopped()){t.data=o;var i=l.apply(e,t._args==n?[t]:[t].concat(t._args));return i===!1&&(t.preventDefault(),t.stopPropagation()),i}},s.i=d.length,d.push(s),"addEventListener"in e&&e.addEventListener(g(s.e),s.proxy,m(s,f))})}function y(t,e,n,i,r){var o=l(t);(e||"").split(/\s/).forEach(function(e){h(t,e,n,i).forEach(function(e){delete s[o][e.i],"removeEventListener"in t&&t.removeEventListener(g(e.e),e.proxy,m(e,r))})})}function j(e,i){return(i||!e.isDefaultPrevented)&&(i||(i=e),t.each(E,function(t,n){var r=i[t];e[t]=function(){return this[n]=x,r&&r.apply(i,arguments)},e[n]=b}),(i.defaultPrevented!==n?i.defaultPrevented:"returnValue"in i?i.returnValue===!1:i.getPreventDefault&&i.getPreventDefault())&&(e.isDefaultPrevented=x)),e}function S(t){var e,i={originalEvent:t};for(e in t)w.test(e)||t[e]===n||(i[e]=t[e]);return j(i,t)}var n,e=1,i=Array.prototype.slice,r=t.isFunction,o=function(t){return"string"==typeof t},s={},a={},u="onfocusin"in window,f={focus:"focusin",blur:"focusout"},c={mouseenter:"mouseover",mouseleave:"mouseout"};a.click=a.mousedown=a.mouseup=a.mousemove="MouseEvents",t.event={add:v,remove:y},t.proxy=function(e,n){var s=2 in arguments&&i.call(arguments,2);if(r(e)){var a=function(){return e.apply(n,s?s.concat(i.call(arguments)):arguments)};return a._zid=l(e),a}if(o(n))return s?(s.unshift(e[n],e),t.proxy.apply(null,s)):t.proxy(e[n],e);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,i){return this.on(t,e,n,i,1)};var x=function(){return!0},b=function(){return!1},w=/^([A-Z]|returnValue$|layer[XY]$)/,E={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,s,a,u,f){var c,l,h=this;return e&&!o(e)?(t.each(e,function(t,e){h.on(t,s,a,e,f)}),h):(o(s)||r(u)||u===!1||(u=a,a=s,s=n),(r(a)||a===!1)&&(u=a,a=n),u===!1&&(u=b),h.each(function(n,r){f&&(c=function(t){return y(r,t.type,u),u.apply(this,arguments)}),s&&(l=function(e){var n,o=t(e.target).closest(s,r).get(0);return o&&o!==r?(n=t.extend(S(e),{currentTarget:o,liveFired:r}),(c||u).apply(o,[n].concat(i.call(arguments,1)))):void 0}),v(r,e,u,a,s,l||c)}))},t.fn.off=function(e,i,s){var a=this;return e&&!o(e)?(t.each(e,function(t,e){a.off(t,i,e)}),a):(o(i)||r(s)||s===!1||(s=i,i=n),s===!1&&(s=b),a.each(function(){y(this,e,s,i)}))},t.fn.trigger=function(e,n){return e=o(e)||t.isPlainObject(e)?t.Event(e):j(e),e._args=n,this.each(function(){"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,n){var i,r;return this.each(function(s,a){i=S(o(e)?t.Event(e):e),i._args=n,i.target=a,t.each(h(a,e.type||e),function(t,e){return r=e.proxy(i),i.isImmediatePropagationStopped()?!1:void 0})}),r},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.trigger(e)}}),["focus","blur"].forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.each(function(){try{this[e]()}catch(t){}}),this}}),t.Event=function(t,e){o(t)||(e=t,t=e.type);var n=document.createEvent(a[t]||"Events"),i=!0;if(e)for(var r in e)"bubbles"==r?i=!!e[r]:n[r]=e[r];return n.initEvent(t,i,!0),j(n)}}(Zepto),function(t){function l(e,n,i){var r=t.Event(n);return t(e).trigger(r,i),!r.isDefaultPrevented()}function h(t,e,i,r){return t.global?l(e||n,i,r):void 0}function p(e){e.global&&0===t.active++&&h(e,null,"ajaxStart")}function d(e){e.global&&!--t.active&&h(e,null,"ajaxStop")}function m(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||h(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void h(e,n,"ajaxSend",[t,e])}function g(t,e,n,i){var r=n.context,o="success";n.success.call(r,t,o,e),i&&i.resolveWith(r,[t,o,e]),h(n,r,"ajaxSuccess",[e,n,t]),y(o,e,n)}function v(t,e,n,i,r){var o=i.context;i.error.call(o,n,e,t),r&&r.rejectWith(o,[n,e,t]),h(i,o,"ajaxError",[n,i,t||e]),y(e,n,i)}function y(t,e,n){var i=n.context;n.complete.call(i,e,t),h(n,i,"ajaxComplete",[e,n]),d(n)}function x(){}function b(t){return t&&(t=t.split(";",2)[0]),t&&(t==f?"html":t==u?"json":s.test(t)?"script":a.test(t)&&"xml")||"text"}function w(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function E(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()||(e.url=w(e.url,e.data),e.data=void 0)}function j(e,n,i,r){return t.isFunction(n)&&(r=i,i=n,n=void 0),t.isFunction(i)||(r=i,i=void 0),{url:e,data:n,success:i,dataType:r}}function T(e,n,i,r){var o,s=t.isArray(n),a=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),r&&(n=i?r:r+"["+(a||"object"==o||"array"==o?n:"")+"]"),!r&&s?e.add(u.name,u.value):"array"==o||!i&&"object"==o?T(e,u,i,n):e.add(n,u)})}var i,r,e=0,n=window.document,o=/)<[^<]*)*<\/script>/gi,s=/^(?:text|application)\/javascript/i,a=/^(?:text|application)\/xml/i,u="application/json",f="text/html",c=/^\s*$/;t.active=0,t.ajaxJSONP=function(i,r){if(!("type"in i))return t.ajax(i);var f,h,o=i.jsonpCallback,s=(t.isFunction(o)?o():o)||"jsonp"+ ++e,a=n.createElement("script"),u=window[s],c=function(e){t(a).triggerHandler("error",e||"abort")},l={abort:c};return r&&r.promise(l),t(a).on("load error",function(e,n){clearTimeout(h),t(a).off().remove(),"error"!=e.type&&f?g(f[0],l,i,r):v(null,n||"error",l,i,r),window[s]=u,f&&t.isFunction(u)&&u(f[0]),u=f=void 0}),m(l,i)===!1?(c("abort"),l):(window[s]=function(){f=arguments},a.src=i.url.replace(/\?(.+)=\?/,"?$1="+s),n.head.appendChild(a),i.timeout>0&&(h=setTimeout(function(){c("timeout")},i.timeout)),l)},t.ajaxSettings={type:"GET",beforeSend:x,success:x,error:x,complete:x,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:u,xml:"application/xml, text/xml",html:f,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},t.ajax=function(e){var n=t.extend({},e||{}),o=t.Deferred&&t.Deferred();for(i in t.ajaxSettings)void 0===n[i]&&(n[i]=t.ajaxSettings[i]);p(n),n.crossDomain||(n.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(n.url)&&RegExp.$2!=window.location.host),n.url||(n.url=window.location.toString()),E(n);var s=n.dataType,a=/\?.+=\?/.test(n.url);if(a&&(s="jsonp"),n.cache!==!1&&(e&&e.cache===!0||"script"!=s&&"jsonp"!=s)||(n.url=w(n.url,"_="+Date.now())),"jsonp"==s)return a||(n.url=w(n.url,n.jsonp?n.jsonp+"=?":n.jsonp===!1?"":"callback=?")),t.ajaxJSONP(n,o);var j,u=n.accepts[s],f={},l=function(t,e){f[t.toLowerCase()]=[t,e]},h=/^([\w-]+:)\/\//.test(n.url)?RegExp.$1:window.location.protocol,d=n.xhr(),y=d.setRequestHeader;if(o&&o.promise(d),n.crossDomain||l("X-Requested-With","XMLHttpRequest"),l("Accept",u||"*/*"),(u=n.mimeType||u)&&(u.indexOf(",")>-1&&(u=u.split(",",2)[0]),d.overrideMimeType&&d.overrideMimeType(u)),(n.contentType||n.contentType!==!1&&n.data&&"GET"!=n.type.toUpperCase())&&l("Content-Type",n.contentType||"application/x-www-form-urlencoded"),n.headers)for(r in n.headers)l(r,n.headers[r]);if(d.setRequestHeader=l,d.onreadystatechange=function(){if(4==d.readyState){d.onreadystatechange=x,clearTimeout(j);var e,i=!1;if(d.status>=200&&d.status<300||304==d.status||0==d.status&&"file:"==h){s=s||b(n.mimeType||d.getResponseHeader("content-type")),e=d.responseText;try{"script"==s?(1,eval)(e):"xml"==s?e=d.responseXML:"json"==s&&(e=c.test(e)?null:t.parseJSON(e))}catch(r){i=r}i?v(i,"parsererror",d,n,o):g(e,d,n,o)}else v(d.statusText||null,d.status?"error":"abort",d,n,o)}},m(d,n)===!1)return d.abort(),v(null,"abort",d,n,o),d;if(n.xhrFields)for(r in n.xhrFields)d[r]=n.xhrFields[r];var S="async"in n?n.async:!0;d.open(n.type,n.url,S,n.username,n.password);for(r in f)y.apply(d,f[r]);return n.timeout>0&&(j=setTimeout(function(){d.onreadystatechange=x,d.abort(),v(null,"timeout",d,n,o)},n.timeout)),d.send(n.data?n.data:null),d},t.get=function(){return t.ajax(j.apply(null,arguments))},t.post=function(){var e=j.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=j.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,i){if(!this.length)return this;var a,r=this,s=e.split(/\s/),u=j(e,n,i),f=u.success;return s.length>1&&(u.url=s[0],a=s[1]),u.success=function(e){r.html(a?t("
").html(e.replace(o,"")).find(a):e),f&&f.apply(r,arguments)},t.ajax(u),this};var S=encodeURIComponent;t.param=function(t,e){var n=[];return n.add=function(t,e){this.push(S(t)+"="+S(e))},T(n,t,e),n.join("&").replace(/%20/g,"+")}}(Zepto),function(t){t.fn.serializeArray=function(){var n,e=[];return t([].slice.call(this.get(0).elements)).each(function(){n=t(this);var i=n.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=i&&"reset"!=i&&"button"!=i&&("radio"!=i&&"checkbox"!=i||this.checked)&&e.push({name:n.attr("name"),value:n.val()})}),e},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(e)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(Zepto),function(t){"__proto__"in{}||t.extend(t.zepto,{Z:function(e,n){return e=e||[],t.extend(e,t.fn),e.selector=n||"",e.__Z=!0,e},isZ:function(e){return"array"===t.type(e)&&"__Z"in e}});try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;window.getComputedStyle=function(t){try{return n(t)}catch(e){return null}}}}(Zepto); +/* Zepto v1.2.0 - zepto event ajax form ie - zeptojs.com/license */ +!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t)}):e(t)}(this,function(t){var e=function(){function $(t){return null==t?String(t):S[C.call(t)]||"object"}function F(t){return"function"==$(t)}function k(t){return null!=t&&t==t.window}function M(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function R(t){return"object"==$(t)}function Z(t){return R(t)&&!k(t)&&Object.getPrototypeOf(t)==Object.prototype}function z(t){var e=!!t&&"length"in t&&t.length,n=r.type(t);return"function"!=n&&!k(t)&&("array"==n||0===e||"number"==typeof e&&e>0&&e-1 in t)}function q(t){return a.call(t,function(t){return null!=t})}function H(t){return t.length>0?r.fn.concat.apply([],t):t}function I(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function V(t){return t in l?l[t]:l[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function _(t,e){return"number"!=typeof e||h[I(t)]?e:e+"px"}function B(t){var e,n;return c[t]||(e=f.createElement(t),f.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),c[t]=n),c[t]}function U(t){return"children"in t?u.call(t.children):r.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function X(t,e){var n,r=t?t.length:0;for(n=0;r>n;n++)this[n]=t[n];this.length=r,this.selector=e||""}function J(t,r,i){for(n in r)i&&(Z(r[n])||L(r[n]))?(Z(r[n])&&!Z(t[n])&&(t[n]={}),L(r[n])&&!L(t[n])&&(t[n]=[]),J(t[n],r[n],i)):r[n]!==e&&(t[n]=r[n])}function W(t,e){return null==e?r(t):r(t).filter(e)}function Y(t,e,n,r){return F(e)?e.call(t,n,r):e}function G(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function K(t,n){var r=t.className||"",i=r&&r.baseVal!==e;return n===e?i?r.baseVal:r:void(i?r.baseVal=n:t.className=n)}function Q(t){try{return t?"true"==t||("false"==t?!1:"null"==t?null:+t+""==t?+t:/^[\[\{]/.test(t)?r.parseJSON(t):t):t}catch(e){return t}}function tt(t,e){e(t);for(var n=0,r=t.childNodes.length;r>n;n++)tt(t.childNodes[n],e)}var e,n,r,i,O,P,o=[],s=o.concat,a=o.filter,u=o.slice,f=t.document,c={},l={},h={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},p=/^\s*<(\w+|!)[^>]*>/,d=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,m=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,g=/^(?:body|html)$/i,v=/([A-Z])/g,y=["val","css","html","text","data","width","height","offset"],x=["after","prepend","before","append"],b=f.createElement("table"),E=f.createElement("tr"),j={tr:f.createElement("tbody"),tbody:b,thead:b,tfoot:b,td:E,th:E,"*":f.createElement("div")},w=/complete|loaded|interactive/,T=/^[\w-]*$/,S={},C=S.toString,N={},A=f.createElement("div"),D={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},L=Array.isArray||function(t){return t instanceof Array};return N.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=A).appendChild(t),r=~N.qsa(i,e).indexOf(t),o&&A.removeChild(t),r},O=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},P=function(t){return a.call(t,function(e,n){return t.indexOf(e)==n})},N.fragment=function(t,n,i){var o,s,a;return d.test(t)&&(o=r(f.createElement(RegExp.$1))),o||(t.replace&&(t=t.replace(m,"<$1>")),n===e&&(n=p.test(t)&&RegExp.$1),n in j||(n="*"),a=j[n],a.innerHTML=""+t,o=r.each(u.call(a.childNodes),function(){a.removeChild(this)})),Z(i)&&(s=r(o),r.each(i,function(t,e){y.indexOf(t)>-1?s[t](e):s.attr(t,e)})),o},N.Z=function(t,e){return new X(t,e)},N.isZ=function(t){return t instanceof N.Z},N.init=function(t,n){var i;if(!t)return N.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&p.test(t))i=N.fragment(t,RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}else{if(F(t))return r(f).ready(t);if(N.isZ(t))return t;if(L(t))i=q(t);else if(R(t))i=[t],t=null;else if(p.test(t))i=N.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}}return N.Z(i,t)},r=function(t,e){return N.init(t,e)},r.extend=function(t){var e,n=u.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){J(t,n,e)}),t},N.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,s=T.test(o);return t.getElementById&&s&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:u.call(s&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},r.contains=f.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},r.type=$,r.isFunction=F,r.isWindow=k,r.isArray=L,r.isPlainObject=Z,r.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},r.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},r.inArray=function(t,e,n){return o.indexOf.call(e,t,n)},r.camelCase=O,r.trim=function(t){return null==t?"":String.prototype.trim.call(t)},r.uuid=0,r.support={},r.expr={},r.noop=function(){},r.map=function(t,e){var n,i,o,r=[];if(z(t))for(i=0;i=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return o.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return F(t)?this.not(this.not(t)):r(a.call(this,function(e){return N.matches(e,t)}))},add:function(t,e){return r(P(this.concat(r(t,e))))},is:function(t){return this.length>0&&N.matches(this[0],t)},not:function(t){var n=[];if(F(t)&&t.call!==e)this.each(function(e){t.call(this,e)||n.push(this)});else{var i="string"==typeof t?this.filter(t):z(t)&&F(t.item)?u.call(t):r(t);this.forEach(function(t){i.indexOf(t)<0&&n.push(t)})}return r(n)},has:function(t){return this.filter(function(){return R(t)?r.contains(this,t):r(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!R(t)?t:r(t)},last:function(){var t=this[this.length-1];return t&&!R(t)?t:r(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?r(t).filter(function(){var t=this;return o.some.call(n,function(e){return r.contains(e,t)})}):1==this.length?r(N.qsa(this[0],t)):this.map(function(){return N.qsa(this,t)}):r()},closest:function(t,e){var n=[],i="object"==typeof t&&r(t);return this.each(function(r,o){for(;o&&!(i?i.indexOf(o)>=0:N.matches(o,t));)o=o!==e&&!M(o)&&o.parentNode;o&&n.indexOf(o)<0&&n.push(o)}),r(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=r.map(n,function(t){return(t=t.parentNode)&&!M(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return W(e,t)},parent:function(t){return W(P(this.pluck("parentNode")),t)},children:function(t){return W(this.map(function(){return U(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||u.call(this.childNodes)})},siblings:function(t){return W(this.map(function(t,e){return a.call(U(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return r.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=B(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=F(t);if(this[0]&&!e)var n=r(t).get(0),i=n.parentNode||this.length>1;return this.each(function(o){r(this).wrapAll(e?t.call(this,o):i?n.cloneNode(!0):n)})},wrapAll:function(t){if(this[0]){r(this[0]).before(t=r(t));for(var e;(e=t.children()).length;)t=e.first();r(t).append(this)}return this},wrapInner:function(t){var e=F(t);return this.each(function(n){var i=r(this),o=i.contents(),s=e?t.call(this,n):t;o.length?o.wrapAll(s):i.append(s)})},unwrap:function(){return this.parent().each(function(){r(this).replaceWith(r(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var n=r(this);(t===e?"none"==n.css("display"):t)?n.show():n.hide()})},prev:function(t){return r(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return r(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;r(this).empty().append(Y(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=Y(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,r){var i;return"string"!=typeof t||1 in arguments?this.each(function(e){if(1===this.nodeType)if(R(t))for(n in t)G(this,n,t[n]);else G(this,t,Y(this,r,e,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(i=this[0].getAttribute(t))?i:e},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){G(this,t)},this)})},prop:function(t,e){return t=D[t]||t,1 in arguments?this.each(function(n){this[t]=Y(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=D[t]||t,this.each(function(){delete this[t]})},data:function(t,n){var r="data-"+t.replace(v,"-$1").toLowerCase(),i=1 in arguments?this.attr(r,n):this.attr(r);return null!==i?Q(i):e},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=Y(this,t,e,this.value)})):this[0]&&(this[0].multiple?r(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(e){if(e)return this.each(function(t){var n=r(this),i=Y(this,e,t,n.offset()),o=n.offsetParent().offset(),s={top:i.top-o.top,left:i.left-o.left};"static"==n.css("position")&&(s.position="relative"),n.css(s)});if(!this.length)return null;if(f.documentElement!==this[0]&&!r.contains(f.documentElement,this[0]))return{top:0,left:0};var n=this[0].getBoundingClientRect();return{left:n.left+t.pageXOffset,top:n.top+t.pageYOffset,width:Math.round(n.width),height:Math.round(n.height)}},css:function(t,e){if(arguments.length<2){var i=this[0];if("string"==typeof t){if(!i)return;return i.style[O(t)]||getComputedStyle(i,"").getPropertyValue(t)}if(L(t)){if(!i)return;var o={},s=getComputedStyle(i,"");return r.each(t,function(t,e){o[e]=i.style[O(e)]||s.getPropertyValue(e)}),o}}var a="";if("string"==$(t))e||0===e?a=I(t)+":"+_(t,e):this.each(function(){this.style.removeProperty(I(t))});else for(n in t)t[n]||0===t[n]?a+=I(n)+":"+_(n,t[n])+";":this.each(function(){this.style.removeProperty(I(n))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(r(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?o.some.call(this,function(t){return this.test(K(t))},V(t)):!1},addClass:function(t){return t?this.each(function(e){if("className"in this){i=[];var n=K(this),o=Y(this,t,e,n);o.split(/\s+/g).forEach(function(t){r(this).hasClass(t)||i.push(t)},this),i.length&&K(this,n+(n?" ":"")+i.join(" "))}}):this},removeClass:function(t){return this.each(function(n){if("className"in this){if(t===e)return K(this,"");i=K(this),Y(this,t,n,i).split(/\s+/g).forEach(function(t){i=i.replace(V(t)," ")}),K(this,i.trim())}})},toggleClass:function(t,n){return t?this.each(function(i){var o=r(this),s=Y(this,t,i,K(this));s.split(/\s+/g).forEach(function(t){(n===e?!o.hasClass(t):n)?o.addClass(t):o.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var n="scrollTop"in this[0];return t===e?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var n="scrollLeft"in this[0];return t===e?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),i=g.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(r(t).css("margin-top"))||0,n.left-=parseFloat(r(t).css("margin-left"))||0,i.top+=parseFloat(r(e[0]).css("border-top-width"))||0,i.left+=parseFloat(r(e[0]).css("border-left-width"))||0,{top:n.top-i.top,left:n.left-i.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||f.body;t&&!g.test(t.nodeName)&&"static"==r(t).css("position");)t=t.offsetParent;return t})}},r.fn.detach=r.fn.remove,["width","height"].forEach(function(t){var n=t.replace(/./,function(t){return t[0].toUpperCase()});r.fn[t]=function(i){var o,s=this[0];return i===e?k(s)?s["inner"+n]:M(s)?s.documentElement["scroll"+n]:(o=this.offset())&&o[t]:this.each(function(e){s=r(this),s.css(t,Y(this,i,e,s[t]()))})}}),x.forEach(function(n,i){var o=i%2;r.fn[n]=function(){var n,a,s=r.map(arguments,function(t){var i=[];return n=$(t),"array"==n?(t.forEach(function(t){return t.nodeType!==e?i.push(t):r.zepto.isZ(t)?i=i.concat(t.get()):void(i=i.concat(N.fragment(t)))}),i):"object"==n||null==t?t:N.fragment(t)}),u=this.length>1;return s.length<1?this:this.each(function(e,n){a=o?n:n.parentNode,n=0==i?n.nextSibling:1==i?n.firstChild:2==i?n:null;var c=r.contains(f.documentElement,a);s.forEach(function(e){if(u)e=e.cloneNode(!0);else if(!a)return r(e).remove();a.insertBefore(e,n),c&&tt(e,function(e){if(!(null==e.nodeName||"SCRIPT"!==e.nodeName.toUpperCase()||e.type&&"text/javascript"!==e.type||e.src)){var n=e.ownerDocument?e.ownerDocument.defaultView:t;n.eval.call(n,e.innerHTML)}})})})},r.fn[o?n+"To":"insert"+(i?"Before":"After")]=function(t){return r(t)[n](this),this}}),N.Z.prototype=X.prototype=r.fn,N.uniq=P,N.deserializeValue=Q,r.zepto=N,r}();return t.Zepto=e,void 0===t.$&&(t.$=e),function(e){function h(t){return t._zid||(t._zid=n++)}function p(t,e,n,r){if(e=d(e),e.ns)var i=m(e.ns);return(a[h(t)]||[]).filter(function(t){return t&&(!e.e||t.e==e.e)&&(!e.ns||i.test(t.ns))&&(!n||h(t.fn)===h(n))&&(!r||t.sel==r)})}function d(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function m(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function g(t,e){return t.del&&!f&&t.e in c||!!e}function v(t){return l[t]||f&&c[t]||t}function y(t,n,i,o,s,u,f){var c=h(t),p=a[c]||(a[c]=[]);n.split(/\s/).forEach(function(n){if("ready"==n)return e(document).ready(i);var a=d(n);a.fn=i,a.sel=s,a.e in l&&(i=function(t){var n=t.relatedTarget;return!n||n!==this&&!e.contains(this,n)?a.fn.apply(this,arguments):void 0}),a.del=u;var c=u||i;a.proxy=function(e){if(e=T(e),!e.isImmediatePropagationStopped()){e.data=o;var n=c.apply(t,e._args==r?[e]:[e].concat(e._args));return n===!1&&(e.preventDefault(),e.stopPropagation()),n}},a.i=p.length,p.push(a),"addEventListener"in t&&t.addEventListener(v(a.e),a.proxy,g(a,f))})}function x(t,e,n,r,i){var o=h(t);(e||"").split(/\s/).forEach(function(e){p(t,e,n,r).forEach(function(e){delete a[o][e.i],"removeEventListener"in t&&t.removeEventListener(v(e.e),e.proxy,g(e,i))})})}function T(t,n){return(n||!t.isDefaultPrevented)&&(n||(n=t),e.each(w,function(e,r){var i=n[e];t[e]=function(){return this[r]=b,i&&i.apply(n,arguments)},t[r]=E}),t.timeStamp||(t.timeStamp=Date.now()),(n.defaultPrevented!==r?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(t.isDefaultPrevented=b)),t}function S(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===r||(n[e]=t[e]);return T(n,t)}var r,n=1,i=Array.prototype.slice,o=e.isFunction,s=function(t){return"string"==typeof t},a={},u={},f="onfocusin"in t,c={focus:"focusin",blur:"focusout"},l={mouseenter:"mouseover",mouseleave:"mouseout"};u.click=u.mousedown=u.mouseup=u.mousemove="MouseEvents",e.event={add:y,remove:x},e.proxy=function(t,n){var r=2 in arguments&&i.call(arguments,2);if(o(t)){var a=function(){return t.apply(n,r?r.concat(i.call(arguments)):arguments)};return a._zid=h(t),a}if(s(n))return r?(r.unshift(t[n],t),e.proxy.apply(null,r)):e.proxy(t[n],t);throw new TypeError("expected function")},e.fn.bind=function(t,e,n){return this.on(t,e,n)},e.fn.unbind=function(t,e){return this.off(t,e)},e.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var b=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,w={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};e.fn.delegate=function(t,e,n){return this.on(e,t,n)},e.fn.undelegate=function(t,e,n){return this.off(e,t,n)},e.fn.live=function(t,n){return e(document.body).delegate(this.selector,t,n),this},e.fn.die=function(t,n){return e(document.body).undelegate(this.selector,t,n),this},e.fn.on=function(t,n,a,u,f){var c,l,h=this;return t&&!s(t)?(e.each(t,function(t,e){h.on(t,n,a,e,f)}),h):(s(n)||o(u)||u===!1||(u=a,a=n,n=r),(u===r||a===!1)&&(u=a,a=r),u===!1&&(u=E),h.each(function(r,o){f&&(c=function(t){return x(o,t.type,u),u.apply(this,arguments)}),n&&(l=function(t){var r,s=e(t.target).closest(n,o).get(0);return s&&s!==o?(r=e.extend(S(t),{currentTarget:s,liveFired:o}),(c||u).apply(s,[r].concat(i.call(arguments,1)))):void 0}),y(o,t,u,a,n,l||c)}))},e.fn.off=function(t,n,i){var a=this;return t&&!s(t)?(e.each(t,function(t,e){a.off(t,n,e)}),a):(s(n)||o(i)||i===!1||(i=n,n=r),i===!1&&(i=E),a.each(function(){x(this,t,i,n)}))},e.fn.trigger=function(t,n){return t=s(t)||e.isPlainObject(t)?e.Event(t):T(t),t._args=n,this.each(function(){t.type in c&&"function"==typeof this[t.type]?this[t.type]():"dispatchEvent"in this?this.dispatchEvent(t):e(this).triggerHandler(t,n)})},e.fn.triggerHandler=function(t,n){var r,i;return this.each(function(o,a){r=S(s(t)?e.Event(t):t),r._args=n,r.target=a,e.each(p(a,t.type||t),function(t,e){return i=e.proxy(r),r.isImmediatePropagationStopped()?!1:void 0})}),i},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(t){e.fn[t]=function(e){return 0 in arguments?this.bind(t,e):this.trigger(t)}}),e.Event=function(t,e){s(t)||(e=t,t=e.type);var n=document.createEvent(u[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),T(n)}}(e),function(e){function p(t,n,r){var i=e.Event(n);return e(t).trigger(i,r),!i.isDefaultPrevented()}function d(t,e,n,i){return t.global?p(e||r,n,i):void 0}function m(t){t.global&&0===e.active++&&d(t,null,"ajaxStart")}function g(t){t.global&&!--e.active&&d(t,null,"ajaxStop")}function v(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||d(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void d(e,n,"ajaxSend",[t,e])}function y(t,e,n,r){var i=n.context,o="success";n.success.call(i,t,o,e),r&&r.resolveWith(i,[t,o,e]),d(n,i,"ajaxSuccess",[e,n,t]),b(o,e,n)}function x(t,e,n,r,i){var o=r.context;r.error.call(o,n,e,t),i&&i.rejectWith(o,[n,e,t]),d(r,o,"ajaxError",[n,r,t||e]),b(e,n,r)}function b(t,e,n){var r=n.context;n.complete.call(r,e,t),d(n,r,"ajaxComplete",[e,n]),g(n)}function E(t,e,n){if(n.dataFilter==j)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function j(){}function w(t){return t&&(t=t.split(";",2)[0]),t&&(t==c?"html":t==f?"json":a.test(t)?"script":u.test(t)&&"xml")||"text"}function T(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function S(t){t.processData&&t.data&&"string"!=e.type(t.data)&&(t.data=e.param(t.data,t.traditional)),!t.data||t.type&&"GET"!=t.type.toUpperCase()&&"jsonp"!=t.dataType||(t.url=T(t.url,t.data),t.data=void 0)}function C(t,n,r,i){return e.isFunction(n)&&(i=r,r=n,n=void 0),e.isFunction(r)||(i=r,r=void 0),{url:t,data:n,success:r,dataType:i}}function O(t,n,r,i){var o,s=e.isArray(n),a=e.isPlainObject(n);e.each(n,function(n,u){o=e.type(u),i&&(n=r?i:i+"["+(a||"object"==o||"array"==o?n:"")+"]"),!i&&s?t.add(u.name,u.value):"array"==o||!r&&"object"==o?O(t,u,r,n):t.add(n,u)})}var i,o,n=+new Date,r=t.document,s=/)<[^<]*)*<\/script>/gi,a=/^(?:text|application)\/javascript/i,u=/^(?:text|application)\/xml/i,f="application/json",c="text/html",l=/^\s*$/,h=r.createElement("a");h.href=t.location.href,e.active=0,e.ajaxJSONP=function(i,o){if(!("type"in i))return e.ajax(i);var c,p,s=i.jsonpCallback,a=(e.isFunction(s)?s():s)||"Zepto"+n++,u=r.createElement("script"),f=t[a],l=function(t){e(u).triggerHandler("error",t||"abort")},h={abort:l};return o&&o.promise(h),e(u).on("load error",function(n,r){clearTimeout(p),e(u).off().remove(),"error"!=n.type&&c?y(c[0],h,i,o):x(null,r||"error",h,i,o),t[a]=f,c&&e.isFunction(f)&&f(c[0]),f=c=void 0}),v(h,i)===!1?(l("abort"),h):(t[a]=function(){c=arguments},u.src=i.url.replace(/\?(.+)=\?/,"?$1="+a),r.head.appendChild(u),i.timeout>0&&(p=setTimeout(function(){l("timeout")},i.timeout)),h)},e.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new t.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:f,xml:"application/xml, text/xml",html:c,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:j},e.ajax=function(n){var u,f,s=e.extend({},n||{}),a=e.Deferred&&e.Deferred();for(i in e.ajaxSettings)void 0===s[i]&&(s[i]=e.ajaxSettings[i]);m(s),s.crossDomain||(u=r.createElement("a"),u.href=s.url,u.href=u.href,s.crossDomain=h.protocol+"//"+h.host!=u.protocol+"//"+u.host),s.url||(s.url=t.location.toString()),(f=s.url.indexOf("#"))>-1&&(s.url=s.url.slice(0,f)),S(s);var c=s.dataType,p=/\?.+=\?/.test(s.url);if(p&&(c="jsonp"),s.cache!==!1&&(n&&n.cache===!0||"script"!=c&&"jsonp"!=c)||(s.url=T(s.url,"_="+Date.now())),"jsonp"==c)return p||(s.url=T(s.url,s.jsonp?s.jsonp+"=?":s.jsonp===!1?"":"callback=?")),e.ajaxJSONP(s,a);var P,d=s.accepts[c],g={},b=function(t,e){g[t.toLowerCase()]=[t,e]},C=/^([\w-]+:)\/\//.test(s.url)?RegExp.$1:t.location.protocol,N=s.xhr(),O=N.setRequestHeader;if(a&&a.promise(N),s.crossDomain||b("X-Requested-With","XMLHttpRequest"),b("Accept",d||"*/*"),(d=s.mimeType||d)&&(d.indexOf(",")>-1&&(d=d.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(d)),(s.contentType||s.contentType!==!1&&s.data&&"GET"!=s.type.toUpperCase())&&b("Content-Type",s.contentType||"application/x-www-form-urlencoded"),s.headers)for(o in s.headers)b(o,s.headers[o]);if(N.setRequestHeader=b,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=j,clearTimeout(P);var t,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==C){if(c=c||w(s.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)t=N.response;else{t=N.responseText;try{t=E(t,c,s),"script"==c?(1,eval)(t):"xml"==c?t=N.responseXML:"json"==c&&(t=l.test(t)?null:e.parseJSON(t))}catch(r){n=r}if(n)return x(n,"parsererror",N,s,a)}y(t,N,s,a)}else x(N.statusText||null,N.status?"error":"abort",N,s,a)}},v(N,s)===!1)return N.abort(),x(null,"abort",N,s,a),N;var A="async"in s?s.async:!0;if(N.open(s.type,s.url,A,s.username,s.password),s.xhrFields)for(o in s.xhrFields)N[o]=s.xhrFields[o];for(o in g)O.apply(N,g[o]);return s.timeout>0&&(P=setTimeout(function(){N.onreadystatechange=j,N.abort(),x(null,"timeout",N,s,a)},s.timeout)),N.send(s.data?s.data:null),N},e.get=function(){return e.ajax(C.apply(null,arguments))},e.post=function(){var t=C.apply(null,arguments);return t.type="POST",e.ajax(t)},e.getJSON=function(){var t=C.apply(null,arguments);return t.dataType="json",e.ajax(t)},e.fn.load=function(t,n,r){if(!this.length)return this;var a,i=this,o=t.split(/\s/),u=C(t,n,r),f=u.success;return o.length>1&&(u.url=o[0],a=o[1]),u.success=function(t){i.html(a?e("
").html(t.replace(s,"")).find(a):t),f&&f.apply(i,arguments)},e.ajax(u),this};var N=encodeURIComponent;e.param=function(t,n){var r=[];return r.add=function(t,n){e.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(t)+"="+N(n))},O(r,t,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;t.getComputedStyle=function(t,e){try{return n(t,e)}catch(r){return null}}}}(),e}); \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/App.java b/app/src/main/java/me/wizos/loread/App.java index d12bace..472743c 100644 --- a/app/src/main/java/me/wizos/loread/App.java +++ b/app/src/main/java/me/wizos/loread/App.java @@ -3,83 +3,139 @@ import android.app.Application; import android.content.Intent; import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.DisplayMetrics; import android.webkit.WebView; +import androidx.work.WorkManager; + import com.bumptech.glide.Glide; +import com.carlt.networklibs.NetType; +import com.carlt.networklibs.NetworkManager; +import com.carlt.networklibs.annotation.NetWork; +import com.carlt.networklibs.utils.Constants; +import com.hjq.toast.ToastUtils; +import com.hjq.toast.style.ToastAliPayStyle; import com.lzy.okgo.OkGo; +import com.orhanobut.logger.AndroidLogAdapter; +import com.orhanobut.logger.Logger; import com.socks.library.KLog; -import com.squareup.leakcanary.LeakCanary; -import com.tencent.bugly.Bugly; import com.tencent.bugly.crashreport.CrashReport; +import com.tencent.stat.MtaSDkException; import com.tencent.stat.StatConfig; +import com.tencent.stat.StatCrashReporter; import com.tencent.stat.StatService; - -import org.greenrobot.eventbus.EventBus; +import com.tencent.stat.common.StatConstants; +import com.yhao.floatwindow.view.FloatWindow; import java.io.File; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import me.wizos.loread.activity.SplashActivity; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.db.Article; -import me.wizos.loread.db.Tag; -import me.wizos.loread.db.dao.DaoMaster; -import me.wizos.loread.db.dao.DaoSession; -import me.wizos.loread.db.dao.SQLiteOpenHelperS; -import me.wizos.loread.event.Sync; -import me.wizos.loread.net.Api; -import me.wizos.loread.net.InoApi; +import me.wizos.loread.adapter.ArticlePagedListAdapter; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.CorePref; +import me.wizos.loread.db.User; +import me.wizos.loread.network.api.AuthApi; +import me.wizos.loread.network.api.BaseApi; +import me.wizos.loread.network.api.FeedlyApi; +import me.wizos.loread.network.api.InoReaderApi; +import me.wizos.loread.network.api.LoreadApi; +import me.wizos.loread.network.api.OAuthApi; +import me.wizos.loread.network.api.TinyRSSApi; import me.wizos.loread.utils.FileUtil; import me.wizos.loread.utils.NetworkUtil; -import me.wizos.loread.utils.TimeUtil; +import me.wizos.loread.utils.ScriptUtil; import me.wizos.loread.utils.Tool; +import me.wizos.loread.view.WebViewS; +import static me.wizos.loread.utils.NetworkUtil.NETWORK_MOBILE; +import static me.wizos.loread.utils.NetworkUtil.NETWORK_NONE; +import static me.wizos.loread.utils.NetworkUtil.NETWORK_WIFI; /** * 在Android中,可以通过继承Application类来实现应用程序级的全局变量,这种全局变量方法相对静态类更有保障,直到应用的所有Activity全部被destory掉之后才会被释放掉。 - * + *

App 类是全局的单例 * Created by Wizos on 2015/12/24. - * */ + */ public class App extends Application implements Thread.UncaughtExceptionHandler { - /** - * 此处的单例不会造成内存泄露,因为 App 本身就是全局的单例 - */ - public static App instance; - public final static int Theme_Day = 0; - public final static int Theme_Night = 1; - - // 跟使用的 API 有关的 字段 - public static long UserID; - public static String StreamId; - public static String StreamTitle; - public static int StreamStatus; - // 这个只是从 Read 属性的4个类型(Readed, UnRead, UnReading, All), Star 属性的3个类型(Stared, UnStar, All)中,生硬的抽出 UnRead(含UnReading), Stared, All 3个快捷状态,供用户在主页面切换时使用 - // 由于根据 StreamId 来获取文章,可从2个属性( Categories[针对Tag], OriginStreamId[针对Feed] )上,共4个变化上(All, Tag, NoTag, Feed)来获取文章。 - // 根据 StreamState 也是从2个属性(ReadState, StarState)的3个快捷状态 ( UnRead[含UnReading], Stared, All ) 来获取文章。 - // 所以文章列表页会有6种组合:某个 Categories 内的 UnRead[含UnReading], Stared, All。某个 OriginStreamId 内的 UnRead[含UnReading], Stared, All。 - // 所有定下来去获取文章的函数也有6个:getArtsUnreadInTag(), getArtsStaredInTag(), getArtsAllInTag(),getUnreadArtsInFeed(), getStaredArtsInFeed(), getAllArtsInFeed() - - public static List

articleList; - public static List tagList = new ArrayList<>(); - - public boolean isSyncing = false; - - public static String cacheRelativePath, boxRelativePath, storeRelativePath; - public static String externalFilesDir; + private static String TAG = "App"; + private static App instance; + public static final String CATEGORY_ALL = "/category/global.all"; + public static final String CATEGORY_UNCATEGORIZED = "/category/global.uncategorized"; + public static final String CATEGORY_TAG = "/category/global.tag"; + public static final String CATEGORY_SEARCH = "/category/global.search"; + + public static final String CATEGORY_STARED = "/tag/global.saved"; + public static final String CATEGORY_MUST = "/category/global.must"; + + public static final String Referer = "Referer"; + + public static final String DISPLAY_RSS = "rss"; + public static final String DISPLAY_READABILITY = "readability"; + public static final String DISPLAY_LINK = "webpage"; + public static final int OPEN_MODE_RSS = 0; + public static final int OPEN_MODE_LINK = 1; + public static final int OPEN_MODE_READABILITY = 2; + + public static final int STATUS_NOT_FILED = 0; + public static final int STATUS_TO_BE_FILED = 1; + public static final int STATUS_IS_FILED = 2; + + public static final String NOT_FILED = "cache"; + public static final String TO_BE_FILED = "box"; + public static final String IS_FILED = "store"; + + public static final int ActivityResult_LoginPageToProvider = 1; + public static final int ActivityResult_ArtToMain = 2; + public static final int ActivityResult_SearchLocalArtsToMain = 3; + + public static final int TYPE_GROUP = 0; + public static final int TYPE_FEED = 1; + + // 状态为所有 + public static final int STATUS_ALL = 0; + // 0 未读 + public static final int STATUS_UNREAD = 1; + // 1 已读 + public static final int STATUS_READED = 2; + // 00 强制未读 + public static final int STATUS_UNREADING = 3; + // 为什么这里不用1和0?因为在用streamStatus时,会综合readStatus和starStatus,如果有重复的就会取不到 + // 类似 StreamIds 将 feed,category,tag 综合考虑,StreamStatus是将 readStatus,starStatus 综合在一起考虑 + public static final int STATUS_STARED = 4; + public static final int STATUS_UNSTAR = 5; + + public static final int MSG_DOUBLE_TAP = -1; + + public int screenWidth; + public int screenHeight; + + public final static int THEME_DAY = 0; + public final static int THEME_NIGHT = 1; + + public ArticlePagedListAdapter articlesAdapter; + + // public boolean isSyncing = false; public static String webViewBaseUrl; + public LinkedHashMap articleProgress = new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > 5; + return size() > 16; } }; - private static DaoSession daoSession; + public static App i() { + if (instance == null) { // 双重锁定,只有在 withDB 还没被初始化的时候才会进入到下一行,然后加上同步锁 + synchronized (App.class) { // 同步锁,避免多线程时可能 new 出两个实例的情况 + if (instance == null) { + instance = new App(); + } + } + } return instance; } @@ -87,58 +143,113 @@ public static App i() { public void onCreate() { super.onCreate(); instance = this; - getDaoSession(); initVar(); - initAutoToggleTheme(); - // 【基础统计API】由于其内部会调用 Looper.prepare() ,不能放在子线程中 - StatService.registerActivityLifecycleCallbacks(this); + ToastUtils.init(this, new ToastAliPayStyle(this)); + + CoreDB.init(instance); + // 【提前初始化 WebView 内核】由于其内部会调用 Looper ,不能放在子线程中 // 链接:https://www.jianshu.com/p/fc7909e24178 - // 经过实际测试,在反复New和销毁WebView时,采用Application要比采用Activity的context要稳定地少用20~30M左右的内存。 - // 虽然他们的内存都维持在一个稳定的消耗水平,但总体看来,Application要比Activity少那么一点。 - // 但是采用Application会影响,在 webview 中打开对话框 - new WebView(this).destroy(); + // 经过测试,采用Application要比采用Activity的context要少用20~30M左右的内存。但采用Application会影响在 webview 中打开对话框。 + WebViewS articleWebView = new WebViewS(this); + articleWebView.destroy(); + + if (BuildConfig.DEBUG) { + WebView.setWebContentsDebuggingEnabled(true); + } + + Logger.addLogAdapter(new AndroidLogAdapter()); - AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + initCrashReport(); + + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override public void run() { + FloatWindow.init(instance); // 初始化网络框架 OkGo.getInstance().init(instance); -// OkGo.getInstance().getOkHttpClient().dispatcher().setMaxRequestsPerHost(3); - OkGo.getInstance().getOkHttpClient().dispatcher().setMaxRequests(3); - // 初始化网络状态 - NetworkUtil.THE_NETWORK = NetworkUtil.getNetWorkState(); // 监听子线程的报错 Thread.setDefaultUncaughtExceptionHandler(instance); // 初始化统计&监控服务 - initLogAndCrash(); -// initLeakCanary(); - - if (Tool.isMainProcess(instance)) { - // 提前初始化 - WithDB.i(); - initApiConfig(); - InoApi.i().initAuthHeaders(); - } + KLog.init(BuildConfig.DEBUG); + // initLeakCanary(); + + ScriptUtil.init(); + + // 初始化网络状态 + NetworkUtil.getNetWorkState(); + + // 尽可能早的进行这一步操作, 建议在 Application 中完成初始化操作 + NetworkManager.getInstance().init(instance); + //注册 + NetworkManager.getInstance().registerObserver(instance); + + // 保险套 + // CondomProcess.installExceptDefaultProcess(instance); } }); } + /** + * Application结束的时候会调用,由系统决定调用的时机 + */ + @Override + public void onTerminate() { + super.onTerminate(); + NetworkManager.getInstance().unRegisterObserver(this); + } + + + //所有网络变化都会被调用,可以通过 NetType 来判断当前网络具体状态 + @NetWork(netType = NetType.AUTO) + public void network(NetType netType) { + switch (netType) { + case WIFI: + KLog.e(Constants.LOG_TAG, "wifi"); + NetworkUtil.setTheNetwork(NETWORK_WIFI); + break; + case CMNET: + case CMWAP: + KLog.e(Constants.LOG_TAG, "4G"); + NetworkUtil.setTheNetwork(NETWORK_MOBILE); + break; + case AUTO: + KLog.e(Constants.LOG_TAG, "自动"); + break; + case NONE: + KLog.e(Constants.LOG_TAG, "无网络"); + NetworkUtil.setTheNetwork(NETWORK_NONE); + break; + default: + break; + } + } + @Override public void uncaughtException(Thread thread, Throwable ex) { - KLog.e("子线程意外报错"); + KLog.e("线程意外报错"); ex.printStackTrace(); - Intent intent = new Intent(this, SplashActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - android.os.Process.killProcess(android.os.Process.myPid()); - startActivity(intent); } - /** * 程序在内存清理的时候执行 + * OnTrimMemory是Android在4.0之后加入的一个回调,任何实现了ComponentCallbacks2接口的类都可以重写实现这个回调方法.OnTrimMemory的主要作用就是指导应用程序在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验. + *

+ * TRIM_MEMORY_COMPLETE (80):内存不足,并且该进程在后台进程列表最后一个,马上就要被清理 + * TRIM_MEMORY_MODERATE (60):内存不足,并且该进程在后台进程列表的中部。 + * TRIM_MEMORY_BACKGROUND (40):内存不足,并且该进程是后台进程。 + * TRIM_MEMORY_UI_HIDDEN (20):内存不足,并且该进程的UI已经不可见了。(应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键导致应用的UI界面不可见) + *

+ * 以上4个是4.0增加 + * TRIM_MEMORY_RUNNING_CRITICAL (15) :内存不足(后台进程不足3个),并且该进程优先级比较高,需要清理内存 + * TRIM_MEMORY_RUNNING_LOW (10) :内存不足(后台进程不足5个),并且该进程优先级比较高,需要清理内存 + * TRIM_MEMORY_RUNNING_MODERATE (5) :内存不足(后台进程超过5个),并且该进程优先级比较高,需要清理内存 + * 以上3个是4.1增加 + *

+ * 作者:Gracker + * 链接:https://www.jianshu.com/p/5b30bae0eb49 */ @Override public void onTrimMemory(int level) { @@ -161,151 +272,167 @@ public void onLowMemory() { Glide.get(this).clearMemory(); } - /** - * 程序终止的时候执行 - */ - @Override - public void onTerminate() { - KLog.i("程序终止的时候执行"); - super.onTerminate(); - } - - public void updateTagList(List temps) { - tagList.clear(); - tagList.addAll(temps); - } - - private void initLogAndCrash() { -// CrashReport.initCrashReport(App.i(), "900044326", BuildConfig.DEBUG); - CrashReport.setIsDevelopmentDevice(instance, BuildConfig.DEBUG); - Bugly.init(getApplicationContext(), "900044326", BuildConfig.DEBUG); - KLog.init(BuildConfig.DEBUG); + private void initCrashReport() { // 腾讯统计,[可选]设置是否打开debug输出,上线时请关闭,Logcat标签为"MtaSDK" StatConfig.setDebugEnable(BuildConfig.DEBUG); + // 【基础统计API】由于其内部会调用 Looper.prepare() ,不能放在子线程中 + StatService.registerActivityLifecycleCallbacks(this); + // 初始化并启动MTA:启动MTA线程,加载数据库配置信息,初始化环境,同时还对多版本SDK进行冲突检测 + try { + // 第三个参数必须为:com.tencent.stat.common.StatConstants.VERSION + StatService.startStatService(this, "AAI4F2S2LM1U", StatConstants.VERSION); + KLog.d("MTA", "MTA初始化成功"); + } catch (MtaSDkException e) { + // MTA初始化失败 + KLog.d("MTA", "MTA初始化失败" + e); + } + // 开启或禁用java异常捕获,初始化不会带来任何的流量和性能消耗。生效后,会注册DefaultUncaughtExceptionHandler,crash时捕获相关信息,存储在本地并上报。 + // 可通过添加StatCrashCallback监听Crash发生。 + StatCrashReporter.getStatCrashReporter(getApplicationContext()).setJavaCrashHandlerStatus(true); + + // 为了保证运营数据的准确性,建议不要在异步线程初始化Bugly。 + CrashReport.initCrashReport(getApplicationContext(), "900044326", BuildConfig.DEBUG); + // 官网现在改用以上的方式 + // Bugly.init(getApplicationContext(), "900044326", BuildConfig.DEBUG); + // 在开发测试阶段,可以在初始化Bugly之前通过以下接口把调试设备设置成“开发设备”。 + CrashReport.setIsDevelopmentDevice(instance, BuildConfig.DEBUG); } - - // 内存泄漏检测工具 - private void initLeakCanary() { - if (LeakCanary.isInAnalyzerProcess(this)) { - return; + public String getWebViewBaseUrl() { + if (TextUtils.isEmpty(webViewBaseUrl)) { + webViewBaseUrl = "file://" + getUserConfigPath(); } - LeakCanary.install(this); + return webViewBaseUrl; } + private void initVar() { + DisplayMetrics outMetrics = getResources().getDisplayMetrics(); + screenWidth = outMetrics.widthPixels; + screenHeight = outMetrics.heightPixels; + } - private void initVar() { - externalFilesDir = getExternalFilesDir(null) + ""; - cacheRelativePath = FileUtil.getRelativeDir(Api.SAVE_DIR_CACHE); - boxRelativePath = FileUtil.getRelativeDir(Api.SAVE_DIR_BOX); - storeRelativePath = FileUtil.getRelativeDir(Api.SAVE_DIR_STORE); - webViewBaseUrl = "file://" + externalFilesDir + "/cache/"; + public String getGlobalAssetsFilesDir() { + return getExternalFilesDir(null) + File.separator + "assets" + File.separator; } - private void initApiConfig() { - // 读取当前的API - - // 读取代理配置 -// readHost(); - // 读取验证 - InoApi.INOREADER_ATUH = WithPref.i().getAuth(); - // 读取uid - UserID = WithPref.i().getUseId(); -// StreamState = WithPref.i().getStreamState(); - StreamStatus = WithPref.i().getStreamStatus(); - - StreamId = WithPref.i().getStreamId(); -// KLog.e(StreamState + " " + StreamId + " "); - if (StreamId == null || StreamId.equals("")) { - StreamId = "user/" + UserID + Api.U_READING_LIST; - } - if (StreamId.equals("user/" + UserID + Api.U_READING_LIST)) { - StreamTitle = getString(R.string.main_activity_title_all); - } else if (StreamId.equals("user/" + UserID + Api.U_NO_LABEL)) { - StreamTitle = getString(R.string.main_activity_title_untag); - } else if (StreamId.startsWith("user/")) { - try { - StreamTitle = WithDB.i().getTag(StreamId).getTitle(); - } catch (Exception e) { - StreamId = "user/" + UserID + Api.U_READING_LIST; - StreamTitle = getString(R.string.main_activity_title_all); - } - } else { - try { - StreamTitle = WithDB.i().getFeed(StreamId).getTitle(); - } catch (Exception e) { - StreamId = "user/" + UserID + Api.U_READING_LIST; - StreamTitle = getString(R.string.main_activity_title_all); - } + public String getGlobalConfigPath() { + return getExternalFilesDir(null) + File.separator + "config" + File.separator; + } + + public String getUserFilesDir() { + if (user == null) { + KLog.e("用户为空"); + Tool.printCallStatck(); + return getExternalFilesDir(null) + "/"; } -// KLog.e("此时StreamId为:" + StreamId + " 此时 Title 为:" + StreamTitle); + return getExternalFilesDir(null) + File.separator + user.getId(); + } + public String getUserConfigPath() { + return getUserFilesDir() + File.separator + "config" + File.separator; } + public String getUserCachePath() { + return getUserFilesDir() + File.separator + "cache" + File.separator; + } + + public String getUserBoxPath() { + return getUserFilesDir() + File.separator + "box" + File.separator; + } + + public String getUserStorePath() { + return getUserFilesDir() + File.separator + "store" + File.separator; + } public void clearApiData() { - WithPref.i().clear(); - WithDB.i().clear(); - FileUtil.deleteHtmlDir(new File(getExternalFilesDir(null) + "/cache/")); + getKeyValue().getString(Contract.UID, null); OkGo.getInstance().cancelAll(); - EventBus.getDefault().post(new Sync(Sync.STOP)); + WorkManager.getInstance(this).cancelAllWork(); + CoreDB.i().articleDao().clear(App.i().getUser().getId()); + CoreDB.i().feedDao().clear(App.i().getUser().getId()); + CoreDB.i().categoryDao().clear(App.i().getUser().getId()); + CoreDB.i().feedCategoryDao().clear(App.i().getUser().getId()); + CoreDB.i().userDao().delete(App.i().getUser().getId()); + FileUtil.deleteHtmlDir(new File(App.i().getUserFilesDir())); } + private BaseApi api; + public User user; - public static boolean hadAutoToggleTheme = false; - protected void initAutoToggleTheme() { -// KLog.e(" 初始化主题" + WithPref.i().isAutoToggleTheme() + TimeUtil.getCurrentHour()); - if (!WithPref.i().isAutoToggleTheme()) { - return; - } - int hour = TimeUtil.getCurrentHour(); - int lastThemeMode = WithPref.i().getThemeMode(); - if (hour >= 7 && hour < 20) { - WithPref.i().setThemeMode(App.Theme_Day); - } else { - WithPref.i().setThemeMode(App.Theme_Night); - } - if (WithPref.i().getThemeMode() != lastThemeMode) { - hadAutoToggleTheme = true; + public User getUser() { + if (user == null) { + String uid = getKeyValue().getString(Contract.UID, null); + if (!TextUtils.isEmpty(uid)) { + user = CoreDB.i().userDao().getById(uid); + } } + return user; } + public CorePref getKeyValue() { + return CorePref.i(); + } - // 官方推荐将获取 DaoMaster 对象的方法放到 Application 层,这样将避免多次创建生成 Session 对象 - public DaoSession getDaoSession() { - if (daoSession == null) { -// DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(i(), DB_NAME, null); - // 此处是为了方便升级 - SQLiteOpenHelperS helper = new SQLiteOpenHelperS(i(), "loread_DB", null); - daoSession = new DaoMaster(helper.getWritableDb()).newSession(); - } - return daoSession; + public AuthApi getAuthApi() { + return (AuthApi) getApi(); } -// private String getUnclassifiedTagId() { -// return "user/" + WithPref.i().getUseId() + Api.U_NO_LABEL; -// } -// -// private String getRootTagId() { -// return "user/" + WithPref.i().getUseId() + Api.U_READING_LIST; -// } + public OAuthApi getOAuthApi() { + return (OAuthApi) getApi(); + } + + public void setApi(BaseApi baseApi) { + this.api = baseApi; + } + public BaseApi getApi() { + if (api == null) { + switch (getUser().getSource()) { + case Contract.PROVIDER_TINYRSS: + TinyRSSApi tinyRSSApi = new TinyRSSApi(); + tinyRSSApi.setAuthorization(getUser().getAuth()); + api = tinyRSSApi; + break; + case Contract.PROVIDER_LOREAD: + LoreadApi loreadApi = new LoreadApi(); + loreadApi.setAuthorization(getUser().getAuth()); + api = loreadApi; + break; + case Contract.PROVIDER_INOREADER: + InoReaderApi inoReaderApi = new InoReaderApi(); + inoReaderApi.setAuthorization(getUser().getAuth()); + api = inoReaderApi; + break; + case Contract.PROVIDER_FEEDLY: + FeedlyApi feedlyApi = new FeedlyApi(); + feedlyApi.setAuthorization(getUser().getAuth()); + api = feedlyApi; + break; + case Contract.PROVIDER_LOCALRSS: + break; + } + KLog.i("初始化 " + getUser().getSource() + " = " + App.i().getUser().getAuth()); + } + return api; + } -// OkHttpClient httpClient; -// public void buildHttpClient() { -// HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(); -// OkHttpClient httpClient = new OkHttpClient.Builder() -// .readTimeout(30000L, TimeUnit.MILLISECONDS) // 默认 10000 -// .writeTimeout(30000L, TimeUnit.MILLISECONDS) // 默认 10000 -// .connectTimeout(30000L, TimeUnit.MILLISECONDS) // 默认 10000 -//// .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager) -// .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) -// .build(); -// } + public void restartApp() { + Intent intent = new Intent(this, SplashActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + android.os.Process.killProcess(android.os.Process.myPid()); + } + // // 内存泄漏检测工具 + // private void initLeakCanary() { + //// if (LeakCanary.isInAnalyzerProcess(this)) { + //// return; + //// } + // LeakCanary.install(this); + // } } diff --git a/app/src/main/java/me/wizos/loread/Contract.java b/app/src/main/java/me/wizos/loread/Contract.java new file mode 100644 index 0000000..88b610c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/Contract.java @@ -0,0 +1,25 @@ +package me.wizos.loread; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class Contract { + public static final String PROVIDER_LOCALRSS = "LocalRSS"; + public static final String PROVIDER_INOREADER = "InoReader"; + public static final String PROVIDER_FEEDLY = "Feedly"; + public static final String PROVIDER_TINYRSS = "TinyTinyRSS"; + public static final String PROVIDER_LOREAD = "Loread"; + public static final String UID = "UID"; + public static final String SCHEMA_HTTP = "http://"; + public static final String SCHEMA_HTTPS = "https://"; + public static final String SCHEMA_FILE = "file://"; + public static final String SCHEMA_LOREAD = "loread://"; + + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + + public static final String isPortrait = "isPortrait"; + // ACCOUNT_TYPE用于我们当前APP获取系统帐户的唯一标识,这个在account_preferences.xml中有,两处的声明必须是一致 + // public static final String ACCOUNT_TYPE = "me.wizos.loreadx"; +} diff --git a/app/src/main/java/me/wizos/loread/activity/ArticleActivity.java b/app/src/main/java/me/wizos/loread/activity/ArticleActivity.java index 92b2a41..e8d6177 100644 --- a/app/src/main/java/me/wizos/loread/activity/ArticleActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/ArticleActivity.java @@ -1,6 +1,7 @@ package me.wizos.loread.activity; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -9,124 +10,138 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; +import android.net.http.SslError; import android.os.Bundle; import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.NonNull; -import android.support.v7.widget.Toolbar; +import android.text.InputType; import android.text.TextUtils; import android.view.KeyEvent; -import android.view.MenuInflater; +import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.webkit.JavascriptInterface; +import android.webkit.SslErrorHandler; import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; -import android.widget.EditText; -import android.widget.PopupMenu; +import android.widget.FrameLayout; import android.widget.ProgressBar; -import android.widget.TextView; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.GravityEnum; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.Theme; +import com.carlt.networklibs.NetType; +import com.carlt.networklibs.utils.NetworkUtils; +import com.hjq.toast.ToastUtils; import com.lzy.okgo.OkGo; import com.lzy.okgo.callback.FileCallback; -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.https.HttpsUtils; import com.lzy.okgo.model.Response; import com.lzy.okgo.request.base.Request; import com.socks.library.KLog; +import org.jetbrains.annotations.NotNull; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.File; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.concurrent.TimeUnit; +import java.util.Arrays; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import cc.shinichi.library.ImagePreview; +import cc.shinichi.library.view.listener.OnBigImageLongClickListener; import me.wizos.loread.App; import me.wizos.loread.BuildConfig; import me.wizos.loread.R; -import me.wizos.loread.bean.config.GlobalConfig; -import me.wizos.loread.common.ImageBridge; -import me.wizos.loread.contentextractor.Extractor; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.data.WithPref; +import me.wizos.loread.bridge.ArticleBridge; +import me.wizos.loread.config.AdBlock; +import me.wizos.loread.config.ArticleTags; +import me.wizos.loread.config.LinkRewriteConfig; +import me.wizos.loread.config.NetworkRefererConfig; +import me.wizos.loread.config.SaveDirectory; +import me.wizos.loread.config.TestConfig; import me.wizos.loread.db.Article; +import me.wizos.loread.db.ArticleTag; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; import me.wizos.loread.db.Feed; import me.wizos.loread.db.Tag; -import me.wizos.loread.net.Api; -import me.wizos.loread.net.DataApi; -import me.wizos.loread.service.SubService; -import me.wizos.loread.utils.DataUtil; -import me.wizos.loread.utils.NetworkUtil; +import me.wizos.loread.network.HttpClientManager; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.ArticleUtil; +import me.wizos.loread.utils.EncryptUtil; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.ImageUtil; +import me.wizos.loread.utils.ScreenUtil; import me.wizos.loread.utils.SnackbarUtil; -import me.wizos.loread.utils.StringUtil; -import me.wizos.loread.utils.ToastUtil; +import me.wizos.loread.utils.StringUtils; +import me.wizos.loread.utils.UriUtil; import me.wizos.loread.view.IconFontView; -import me.wizos.loread.view.SlideArrow.SlideArrowLayout; import me.wizos.loread.view.SwipeRefreshLayoutS; import me.wizos.loread.view.WebViewS; import me.wizos.loread.view.colorful.Colorful; -import me.wizos.loread.view.webview.AdBlock; +import me.wizos.loread.view.slideback.SlideBack; +import me.wizos.loread.view.slideback.SlideLayout; +import me.wizos.loread.view.slideback.callback.SlideCallBack; +import me.wizos.loread.view.webview.DownloadListenerS; +import me.wizos.loread.view.webview.LongClickPopWindow; import me.wizos.loread.view.webview.SlowlyProgressBar; import me.wizos.loread.view.webview.VideoImpl; import okhttp3.Call; -import okhttp3.FormBody; +import okhttp3.Callback; +import okhttp3.MediaType; import okhttp3.OkHttpClient; import top.zibin.luban.CompressionPredicate; +import top.zibin.luban.InputStreamProvider; import top.zibin.luban.Luban; import top.zibin.luban.OnCompressListener; import top.zibin.luban.OnRenameListener; +import static me.wizos.loread.Contract.SCHEMA_FILE; +import static me.wizos.loread.Contract.SCHEMA_HTTP; +import static me.wizos.loread.Contract.SCHEMA_HTTPS; + + /** * @author Wizos on 2017 */ +@SuppressWarnings("unchecked") @SuppressLint("SetJavaScriptEnabled") -public class ArticleActivity extends BaseActivity implements ImageBridge, SlideArrowLayout.SlideListener { - protected static final String TAG = "ArticleActivity"; - +public class ArticleActivity extends BaseActivity implements ArticleBridge { + private static final String TAG = "ArticleActivity"; private SwipeRefreshLayoutS swipeRefreshLayoutS; - // private SmartRefreshLayout swipeRefreshLayoutS; private SlowlyProgressBar slowlyProgressBar; - private IconFontView starView, readView, saveView; + private IconFontView starView, readView, saveView, readabilityView; private WebViewS selectedWebView; - private SlideArrowLayout entryView; + // private MirrorSwipeBackLayout entryView; + // private RefreshLayout entryView; + private FrameLayout entryView; + private SlideLayout slideLayout; private Toolbar toolbar; + private RelativeLayout bottomBar; private VideoImpl video; private Article selectedArticle; private int articleNo; private String articleId; - private TextView articleNumView; - private int articleCount; - - public Handler articleHandler = new Handler(new Handler.Callback() { - @Override - public boolean handleMessage(Message msg) { - if (msg.what == 0 && selectedWebView != null && selectedWebView.getProgress() < 100) { - selectedWebView.stopLoading(); - canSlide = true; - } - return false; - } - }); - - /** - * Video 视频播放类 - */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -134,7 +149,7 @@ protected void onCreate(Bundle savedInstanceState) { Bundle bundle; if (savedInstanceState != null) { bundle = savedInstanceState; - App.i().articleProgress.put(articleId, bundle.getInt("articleProgress")); + App.i().articleProgress.put(bundle.getString("articleId"), bundle.getInt("articleProgress")); } else { bundle = getIntent().getExtras(); } @@ -142,21 +157,17 @@ protected void onCreate(Bundle savedInstanceState) { // 文章在列表中的位置编号,下标从 0 开始 articleNo = bundle.getInt("articleNo"); // 列表中所有的文章数目 - articleCount = bundle.getInt("articleCount"); + // articleCount = bundle.getInt("articleCount"); articleId = bundle.getString("articleId"); -// articleIDs = bundle.getStringArrayList("articleIDs"); -// KLog.e("开始初始化数据2" + articleNo + "==" + articleCount + "==" + articleId + " == " + articleIDs ); + // KLog.e("开始初始化数据2" + articleNo + "==" + articleCount + "==" + articleId + " == " + articleIDs ); initToolbar(); initView(); // 初始化界面上的 View,将变量映射到布局上。 initSelectedPage(articleNo); - startService(new Intent(this, SubService.class)); + imgHttpClient = HttpClientManager.i().imageHttpClient(); } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - } + public static Handler articleHandler = new Handler(); @Override public void onResume() { @@ -172,13 +183,14 @@ public void onPause() { @Override protected void onDestroy() { + saveArticleProgress(); + OkGo.cancelAll(imgHttpClient); + + // KLog.e("onDestroy:" + selectedWebView); // 如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。 - // 这样做的好处是在 Acticity 退出的时候,可以避免内存泄露。因为 handler 内可能引用 Activity ,导致 Activity 退出后,内存泄漏 -// KLog.e("onDestroy:" + selectedWebView); - OkGo.cancelAll(articleHttpClient); + // 这样做的好处是在 Acticity 退出的时候,可以避免内存泄露。因为 handler 内可能引用 Activity ,导致 Activity 退出后,内存泄漏。 articleHandler.removeCallbacksAndMessages(null); entryView.removeAllViews(); - saveArticleProgress(); selectedWebView.destroy(); selectedWebView = null; super.onDestroy(); @@ -195,11 +207,12 @@ public int saveArticleProgress() { @Override protected void onSaveInstanceState(Bundle outState) { - outState.putInt("articleNo", 0); + outState.putInt("articleNo", articleNo); outState.putInt("articleCount", 1); outState.putString("articleId", articleId); outState.putInt("articleProgress", saveArticleProgress()); - KLog.e("自动保存:" + articleNo + "==" + "==" + articleId); + outState.putInt("theme", App.i().getUser().getThemeMode()); + //KLog.i("自动保存:" + articleNo + "==" + "==" + articleId); super.onSaveInstanceState(outState); } @@ -207,133 +220,359 @@ protected void onSaveInstanceState(Bundle outState) { @JavascriptInterface @Override public void log(String paramString) { - KLog.e("ImageBridge", "【log】" + paramString); + KLog.e(ArticleBridge.TAG, "【log】" + paramString); } + @JavascriptInterface + @Override + public void readImage(String articleId, String imgId, String originalUrl) { + String idInMD5 = EncryptUtil.MD5(articleId); + String cacheUrl = FileUtil.readCacheFilePath(idInMD5, originalUrl); + articleHandler.post(new Runnable() { + @Override + public void run() { + if (TextUtils.isEmpty(cacheUrl)) { + if (!NetworkUtils.isAvailable()) { + selectedWebView.loadUrl("javascript:setTimeout( onImageLoadFailed('" + imgId + "'),1 )"); + } else if (App.i().getUser().isDownloadImgOnlyWifi() && !NetworkUtils.getNetType().equals(NetType.WIFI)) { + selectedWebView.loadUrl("javascript:setTimeout( onImageLoadNeedClick('" + imgId + "'),1 )"); + }else { + selectedWebView.loadUrl("javascript:setTimeout( onImageLoading('" + imgId + "'),1 )"); + downImage(articleId, imgId, originalUrl, false); + } + }else { + if(ImageUtil.isImg(new File(cacheUrl))){ + selectedWebView.loadUrl("javascript:setTimeout( onImageLoadSuccess('" + imgId + "','" + cacheUrl + "'),1)"); + }else { + selectedWebView.loadUrl("javascript:setTimeout( onImageError('" + imgId + "'),1 )"); + KLog.e("加载图片", "缓存文件读取失败:不是图片"); + } + } + } + }); + } @JavascriptInterface @Override - public void loadImage(String articleId, int index, String url, final String originalUrl) { - // 去掉已经缓存了图片的 url -// KLog.e("loadImage", "【log】" + articleId + " "+ index + " - " + url + " " + originalUrl); - if (!url.startsWith("file:///android_asset/") || TextUtils.isEmpty(articleId) || !selectedArticle.getId().equals(articleId)) { + public void openImage(String articleId, String imageFilePath) { + KLog.e(ArticleBridge.TAG, "打开图片:" + this.getPackageName() + " , " + imageFilePath + " " ); + // 如果是 svg 格式的图片则点击无反应 + if(imageFilePath.endsWith(".svg")){ return; } - // 1.网络不可用 → 返回失败占位图 - if (!NetworkUtil.isNetworkAvailable()) { - selectedWebView.post(new Runnable() { - @Override - public void run() { - selectedWebView.loadUrl("javascript:onImageLoadFailed('" + originalUrl + "')"); + + // 如果传入的是缩略图的文件地址,则替换为大图的 + Pattern P_COMPRESSED = Pattern.compile(this.getPackageName() + "/files/(.*?)/compressed/", Pattern.CASE_INSENSITIVE); + Matcher m = P_COMPRESSED.matcher(imageFilePath); + if (m.find()) { + String id = m.group(1); + imageFilePath = m.replaceFirst(this.getPackageName() + "/files/" + id + "/original/"); + } + + final String imgUri = imageFilePath; + + ImagePreview.getInstance() + // 上下文,必须是activity,不需要担心内存泄漏,本框架已经处理好; + .setContext(ArticleActivity.this) + // 设置从第几张开始看(索引从0开始) + .setIndex(0) + //================================================================================================= + // 有三种设置数据集合的方式,根据自己的需求进行三选一: + // 1:第一步生成的imageInfo List + // .setImageInfoList(imageInfoList) + // 2:直接传url List + //.setImageList(List imageList) + // 3:只有一张图片的情况,可以直接传入这张图片的url + .setImage(imgUri) + + // 保存的文件夹名称,会在SD卡根目录进行文件夹的新建。 + // (你也可设置嵌套模式,比如:"BigImageView/Download",会在SD卡根目录新建BigImageView文件夹,并在BigImageView文件夹中新建Download文件夹) + .setFolderName(getString(R.string.app_name)) + .setLoadStrategy(ImagePreview.LoadStrategy.AlwaysOrigin) + // 缩放动画时长,单位ms +// .setZoomTransitionDuration(300) + // 是否启用上拉/下拉关闭。默认不启用 + .setEnableDragClose(true) + // 长按回调 + .setBigImageLongClickListener(new OnBigImageLongClickListener() { + @Override + public boolean onLongClick(Activity activity, View view, int position) { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(imgUri)); + shareIntent.setType("image/*"); + + //shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_img)); + //shareIntent.putExtra(Intent.EXTRA_TEXT,getString(R.string.share_img)); + //shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_img))); + return false; + } + }) + // 开启预览 + .start(); + } + + private static class MyCompressionPredicate implements CompressionPredicate { + @Override + public boolean apply(String preCompressedPath, InputStreamProvider path) { +// KLog.e("检测是否要压缩图片:" + preCompressedPath); + try { + if (preCompressedPath.toLowerCase().endsWith(".gif")) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = 1; + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(path.open(), null, options); +// KLog.e("压缩图片,忽略压缩:" + preCompressedPath + options.outWidth ); + return options.outWidth >= 300 || options.outHeight >= 300; + } else { + return true; } - }); - } else if (WithPref.i().isDownImgOnlyWifi() && !NetworkUtil.isWiFiUsed()) { - selectedWebView.post(new Runnable() { + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + } + + private static class DownFileCallback extends FileCallback { + private WeakReference weakReferenceContext; + private WeakReference selectedWebView; + private String originalFileDir; + private String fileNameExt; + private String compressedFileDir; + private String imgId; + + private String imageUrl; + private String articleUrl; + private boolean guessReferer; + + DownFileCallback(String destFileDir, String destFileName) { + super(destFileDir, destFileName); + this.originalFileDir = destFileDir; + this.imgId = destFileName; + } + + void setParam(Context context, WebViewS webView, String compressedFileDir, String fileNameExt, boolean guessReferer) { + this.weakReferenceContext = new WeakReference(context); + this.selectedWebView = new WeakReference(webView); + this.compressedFileDir = compressedFileDir; + this.fileNameExt = fileNameExt; + this.guessReferer = guessReferer; + } + + void setRefererParam(String originalUrl, String articleUrl, boolean guessReferer) { + this.imageUrl = originalUrl; + this.articleUrl = articleUrl; + this.guessReferer = guessReferer; + } + + @Override + public void onSuccess(Response response) { + MediaType mediaType = response.getRawResponse().body().contentType(); + if( mediaType != null ){ + if( mediaType.subtype().contains("svg") && !fileNameExt.endsWith(".svg")){ + fileNameExt = fileNameExt + ".svg"; + } + } + File downloadedOriginalFile = response.body(); + if(!ImageUtil.isImg(downloadedOriginalFile)){ + downloadedOriginalFile.delete(); + if (selectedWebView.get() != null) { + selectedWebView.get().loadUrl("javascript:setTimeout( onImageError('" + imgId + "'),1)"); + } + return; + }else if(guessReferer){ // 当是根据系统自动猜得的referer而成功下载到图片时,保存自动识别的refer而规则 + NetworkRefererConfig.i().addReferer(imageUrl, articleUrl); + } + + + File targetOriginalFile = new File(originalFileDir + fileNameExt); + + // 可能存在图片的文件名相同,但是实际是不同图片的情况。 + if(targetOriginalFile.exists() && downloadedOriginalFile.length() != downloadedOriginalFile.length()){ + fileNameExt = imgId + "_" + fileNameExt; + targetOriginalFile = new File(originalFileDir + fileNameExt); + } + downloadedOriginalFile.renameTo(targetOriginalFile); + + final File finalTargetFile = targetOriginalFile; + + if (selectedWebView.get() == null) { + return; + } +// KLog.i("下载图片成功,准备压缩:" + originalFileDir + prefix + fileNameExt + svgExt + "," + originalUrl ); + + Luban.with(App.i()) + .load(targetOriginalFile) + .ignoreBy(100) // 忽略100kb以下的文件 + // 缓存压缩图片路径 + // .setTargetPath(compressedFileDir + fileNameExt) + .setTargetDir(compressedFileDir) + .setMaxSiz(App.i().screenWidth, App.i().screenHeight) + // 设置开启压缩条件。当路径为空或者为gif时,不压缩 + // 压缩后会改变文件地址,所以改回来 + .setRenameListener(new OnRenameListener() { + @Override + public String rename(String filePath) { + return fileNameExt; + } + }) + .filter(new MyCompressionPredicate()) + .setCompressListener(new OnCompressListener() { + @Override + public void onStart() { + } + + @Override + public void onUnChange(final File file) { +// KLog.e("没有压缩图片:" + Thread.currentThread() + " " + file.getPath() + " " + compressedFileDir); + articleHandler.post(new Runnable() { + @Override + public void run() { + if (selectedWebView.get() != null) { + selectedWebView.get().loadUrl("javascript:setTimeout( onImageLoadSuccess('" + imgId + "','" + file.getPath() + "'),1 )"); + } + } + }); + } + + @Override + public void onSuccess(final File file) { + ImageUtil.mergeBitmap(weakReferenceContext, file, new ImageUtil.OnMergeListener() { + @Override + public void onSuccess() { +// KLog.e("图片合成成功" + Thread.currentThread()); + articleHandler.post(new Runnable() { + @Override + public void run() { + if (selectedWebView.get() != null) { + selectedWebView.get().loadUrl("javascript:setTimeout( onImageLoadSuccess('" + imgId + "','" + file.getPath() + "'),1 )"); + } + } + }); + } + + @Override + public void onError(Throwable e) { + KLog.e("合成图片报错:" + Thread.currentThread()); + e.printStackTrace(); + articleHandler.post(new Runnable() { + @Override + public void run() { + if (selectedWebView.get() != null) { + selectedWebView.get().loadUrl("javascript:setTimeout( onImageLoadSuccess('" + imgId + "','" + finalTargetFile.getPath() + "'),1)"); + } + } + }); + } + }); + } + + @Override + public void onError(Throwable e) { + KLog.e("压缩图片报错" + Thread.currentThread() ); +// selectedWebView.loadUrl("javascript:onImageLoadSuccess('" + originalUrl + "','" + originalFileDir + fileNameExt + "')"); + articleHandler.post(new Runnable() { + @Override + public void run() { + if (selectedWebView.get() != null) { + selectedWebView.get().loadUrl("javascript:setTimeout( onImageLoadSuccess('" + imgId + "','" + finalTargetFile.getPath() + "'),1)"); + } + } + }); + } + }).launch(); + } + + // 该方法执行在主线程中 + @Override + public void onError(Response response) { + new File(originalFileDir + imgId).delete(); + KLog.e("下载图片失败:" + imageUrl + "','" + response.code() + " " + response.getException()); + articleHandler.post(new Runnable() { @Override public void run() { - selectedWebView.loadUrl("javascript:onImageLoadNeedClick('" + originalUrl + "')"); + if (selectedWebView.get() != null) { + selectedWebView.get().loadUrl("javascript:setTimeout( onImageLoadFailed('" + imgId + "'),1)"); + } } }); - } else { - downImage(articleId, index, originalUrl); } } + @JavascriptInterface @Override - public void openImage(String articleId, String imageFilePath, int index) { - KLog.e("ImageBridge", "打开图片" + imageFilePath + index); - // 过滤空格回车标签 + public void downImage(String articleId, String imgId, String originalUrl, boolean guessReferer) { + String articleIdInMD5 = EncryptUtil.MD5(articleId); + String originalFileDir = App.i().getUserCachePath() + articleIdInMD5 + "/original/"; + String fileNameExt = UriUtil.guessFileNameExt(originalUrl); - Pattern P_COMPRESSED = Pattern.compile("me\\.wizos\\.loread/files/cache/(.*?)/compressed", Pattern.CASE_INSENSITIVE); - Matcher m = P_COMPRESSED.matcher(imageFilePath); - if (m.find()) { - String id = m.group(1); - imageFilePath = m.replaceFirst("me.wizos.loread/files/cache/" + id + "/" + id + "_files"); - if (!new File(imageFilePath).exists()) { - imageFilePath = m.replaceFirst("me.wizos.loread/files/cache/" + id + "/original"); - } + // 下载时的过渡名称为 imgId + if (new File(originalFileDir + imgId).exists() || new File(originalFileDir + fileNameExt).exists()) { + return; } - // 直接打开内置图片浏览器 - Intent intent = new Intent(ArticleActivity.this, ImageActivity.class); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setDataAndType(Uri.fromFile(new File(imageFilePath)), "image/*"); - startActivity(intent); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - - // 调用系统默认的图片查看应用 -// Intent intentImage = new Intent(Intent.ACTION_VIEW); -// intentImage.addCategory(Intent.CATEGORY_DEFAULT); -// File file = new File(imageFilePath); -// intentImage.setDataAndType(Uri.fromFile(file), "image/*"); -// startActivity(intentImage); + String compressedFileDir = App.i().getUserCachePath() + articleIdInMD5 + "/compressed/"; + DownFileCallback fileCallback = new DownFileCallback(originalFileDir, imgId); + fileCallback.setParam(App.i(), selectedWebView, compressedFileDir, fileNameExt, guessReferer); + fileCallback.setRefererParam(originalUrl, selectedArticle.getLink(), guessReferer); - // 每次都要选择打开方式 -// startActivity(Intent.createChooser(intentImage, "请选择一款")); + Request request = OkGo.get(originalUrl) + .tag(articleId) + .client(imgHttpClient); + + if( guessReferer ){ + request.headers("referer", selectedArticle.getLink()); + KLog.i("来源策略", "图片策略:" + true); + }else { + String referer = NetworkRefererConfig.i().guessRefererByUrl(originalUrl); + if (!StringUtils.isEmpty(referer)) { + request.headers("referer", referer); + KLog.i("来源策略", "来源:" + referer); + } + } - // 调起系统默认的图片查看应用(带有选择为默认) -// if(BuildConfig.DEBUG){ -// Intent openImageIntent = new Intent(Intent.ACTION_VIEW); -// openImageIntent.addCategory(Intent.CATEGORY_DEFAULT); -// openImageIntent.setDataAndType(Uri.fromFile(new File(imageFilePath)), "image/*"); -// getDefaultActivity(openImageIntent); -// } + request.execute(fileCallback); + KLog.e("下载图片:" + originalUrl); } -// // 获取默认的打开方式 -// public void getDefaultActivity(Intent intent) { -// PackageManager pm = this.getPackageManager(); -// ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); -// // 如果本应用没有询问过是否要选择默认打开方式,并且没有默认的打开方式,打开默认方式选择狂 -// if (!WithPref.i().hadAskImageOpenMode() || info.activityInfo.packageName.equals("android")) { -// WithPref.i().setHadAskImageOpenMode(true); -// intent.setComponent(new ComponentName("android", "com.android.internal.app.ResolverActivity")); -// } -// startActivity(intent); -// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); -// KLog.i("打开方式", "默认打开方式信息 = " + info + ";pkgName = " + info.activityInfo.packageName); -// } - - // 打开选择默认打开方式的弹窗 -// public void startChooseDialog() { -// Intent intent = new Intent(); -// intent.setAction("android.intent.action.VIEW"); -// intent.addCategory(Intent.CATEGORY_DEFAULT); -// intent.setData(Uri.fromFile(new File(imageFilePath))); -// intent.setComponent(new ComponentName("android","com.android.internal.app.ResolverActivity")); -// startActivity(intent); -// } - @JavascriptInterface @Override - public void openLink(String link) { - if (WithPref.i().isSysBrowserOpenLink()) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); - startActivity(Intent.createChooser(intent, "选择打开方式")); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - } else { - Intent intent = new Intent(ArticleActivity.this, WebActivity.class); - intent.setData(Uri.parse(link)); - intent.putExtra("theme", WithPref.i().getThemeMode()); - intent.putExtra("title", selectedArticle.getTitle()); - startActivity(intent); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + public void openLink(String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + // 使用内置浏览器 + if( !App.i().getUser().isOpenLinkBySysBrowser() && (url.startsWith(SCHEMA_HTTP) || url.startsWith(SCHEMA_HTTPS)) && useInnerBrowser(intent) ){ + intent = new Intent(ArticleActivity.this, WebActivity.class); + intent.setData(Uri.parse(url)); + intent.putExtra("theme", App.i().getUser().getThemeMode()); } + //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED ); + // 添加这一句表示对目标应用临时授权该Uri所代表的文件 + // intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + + private boolean useInnerBrowser(Intent intent){ + return getMatchActivitiesSize(intent) == getMatchActivitiesSize(new Intent(Intent.ACTION_VIEW, Uri.parse("https://wizos.me"))); + } + private int getMatchActivitiesSize(Intent intent){ + return getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size(); } - /** - * 尝试注入JS,执行setupImage()方法,但是可能因为渲染html比较慢,导致setupImage没有真正执行。 - * 其实主要是在js文件加载中,直接调用tryInitJs,并且只初始化当前页面的setupImage - * - * @param articleId - */ @JavascriptInterface @Override - public void tryInitJs(String articleId) { + public void openAudio(String url) { + Intent intent = new Intent(this, MusicActivity.class); + intent.putExtra("title", selectedArticle.getTitle()); + intent.setData(Uri.parse(url)); + startActivity(intent); } - @JavascriptInterface @Override public void readability() { @@ -343,178 +582,210 @@ public void run() { onReadabilityClick(); } }); - } private void initView() { starView = findViewById(R.id.article_bottombar_star); + starView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + editFavorites(App.i().getUser().getId()); + return true; + } + }); readView = findViewById(R.id.article_bottombar_read); saveView = findViewById(R.id.article_bottombar_save); swipeRefreshLayoutS = findViewById(R.id.art_swipe_refresh); swipeRefreshLayoutS.setEnabled(false); - articleNumView = findViewById(R.id.art_toolbar_num); -// swipeRefreshLayoutS.setOnRefreshListener(new OnRefreshListener() { -// @Override -// public void onRefresh(RefreshLayout refreshlayout) { -// refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败 -// } -// }); if (BuildConfig.DEBUG) { saveView.setVisibility(View.VISIBLE); saveView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - onSaveClick(view); - } - }); - articleNumView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - showArticleInfo(view); + onClickSaveIcon(view); } }); } - entryView = findViewById(R.id.test_framelayout); - entryView.setSlideListener(this); - entryView.setActivity(this); - } + entryView = findViewById(R.id.slide_arrow_layout); + slideLayout = findViewById(R.id.art_slide_layout); + + // KLog.e("子数量" + slideLayout.getChildCount() ); + + int color; + if (App.i().getUser().getThemeMode() == App.THEME_DAY) { + color = Color.BLACK; + } else { + color = Color.WHITE; + } + slideLayout.edgeMode(SlideBack.EDGE_BOTH).arrowColor(color).callBack(new SlideCallBack() { + @Override + public void onSlide(int edgeFrom) { + if (edgeFrom == SlideBack.EDGE_LEFT) { + onLeftBack(); + entryView.scrollBy(0, 0); + } else { + onRightBack(); + entryView.scrollBy(0, 0); + } + } + @Override + public void onViewSlide(int edgeFrom, int offset) { + //KLog.e("拖动方向:" + edgeFrom + " , " + offset); + if (edgeFrom == SlideBack.EDGE_LEFT) { + entryView.scrollTo(-offset, 0); + } else { + entryView.scrollTo(offset, 0); + } + } + }).register(); + } private void initToolbar() { + bottomBar = findViewById(R.id.art_bottombar); toolbar = findViewById(R.id.art_toolbar); setSupportActionBar(toolbar); + // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 getSupportActionBar().setHomeButtonEnabled(true); + // 决定左上角图标的左侧是否有向左的小箭头,true 有小箭头 getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(false); slowlyProgressBar = new SlowlyProgressBar((ProgressBar) findViewById(R.id.article_progress_bar)); toolbar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (articleHandler.hasMessages(Api.MSG_DOUBLE_TAP) && selectedWebView != null) { - articleHandler.removeMessages(Api.MSG_DOUBLE_TAP); + if (articleHandler.hasMessages(App.MSG_DOUBLE_TAP) && selectedWebView != null) { + articleHandler.removeMessages(App.MSG_DOUBLE_TAP); selectedWebView.scrollTo(0, 0); } else { - articleHandler.sendEmptyMessageDelayed(Api.MSG_DOUBLE_TAP, ViewConfiguration.getDoubleTapTimeout()); + articleHandler.sendEmptyMessageDelayed(App.MSG_DOUBLE_TAP, ViewConfiguration.getDoubleTapTimeout()); } } }); } - @Override - public void slideLeftSuccess() { - if (articleNo + 1 >= App.articleList.size()) { + public void onLeftBack() { + if (App.i().articlesAdapter == null || articleNo - 1 < 0) { + ToastUtils.show("没有文章了"); return; } saveArticleProgress(); - articleNo = articleNo + 1; + articleNo = articleNo - 1; initSelectedPage(articleNo); } - private boolean canSlide = true; - - @Override - public void slideRightSuccess() { - if (articleNo - 1 < 0) { + public void onRightBack() { + if (App.i().articlesAdapter == null || articleNo + 1 >= App.i().articlesAdapter.getItemCount()) { + ToastUtils.show("没有文章了"); return; } saveArticleProgress(); - articleNo = articleNo - 1; + articleNo = articleNo + 1; initSelectedPage(articleNo); } - @Override - public boolean canSlideLeft() { - return !(articleNo + 1 >= App.articleList.size() || !canSlide); - } - - @Override - public boolean canSlideRight() { - return !(articleNo - 1 < 0 || !canSlide); - } - public void initSelectedPage(int position) { swipeRefreshLayoutS.setRefreshing(false); reInitSelectedArticle(position); - initSelectedWebViewContent(position); -// KLog.e("initSelectedPage结束。位置:" + position + " " + selectedWebView); + initSelectedWebViewContent(); } public void reInitSelectedArticle(int position) { - // 取消之前那篇文章的图片下载。但是如果回到之前那篇文章,怎么恢复下载呢? - OkGo.cancelTag(articleHttpClient, articleId); - if (App.articleList != null) { - selectedArticle = App.articleList.get(position); + // 取消之前那篇文章的图片下载(但是如果回到之前那篇文章,怎么恢复下载呢?) + OkGo.cancelTag(imgHttpClient, articleId); + articleNo = position; + if (App.i().articlesAdapter != null && position < App.i().articlesAdapter.getItemCount()) { + selectedArticle = App.i().articlesAdapter.getItem(position); +// selectedArticle = CoreDB.i().articleDao().getById(App.i().getUser().getId(),App.i().articlesAdapter.getArticleId(position)); articleId = selectedArticle.getId(); } else { - selectedArticle = WithDB.i().getArticle(articleId); + selectedArticle = CoreDB.i().articleDao().getById(App.i().getUser().getId(), articleId); } -// articleId = articleIDs.get(position); - articleNo = position; - - initIconState(position); + initIconState(); initFeedConfig(); } + private int downX, downY; // (webview在实例化后,可能还在渲染html,不一定能执行js) - private void initSelectedWebViewContent(final int position) { + @SuppressLint("ClickableViewAccessibility") + private void initSelectedWebViewContent() { if (selectedWebView == null) { selectedWebView = new WebViewS(new MutableContextWrapper(App.i())); -// selectedWebView = App.i().articleWebView; - entryView.addView(selectedWebView, 0); + entryView.removeAllViews(); + entryView.addView(selectedWebView); // 初始化视频处理类 video = new VideoImpl(ArticleActivity.this, selectedWebView); - selectedWebView.setWebChromeClient(new WebChromeClientX(video)); + selectedWebView.setWebChromeClient(new WebChromeClientX(video, new WeakReference(slowlyProgressBar))); selectedWebView.setWebViewClient(new WebViewClientX()); // 原本想放在选择 webview 页面的时候去加载,但可能由于那时页面内容已经加载所以无法设置下面这个JSInterface? - selectedWebView.addJavascriptInterface(ArticleActivity.this, "ImageBridge"); + selectedWebView.addJavascriptInterface(ArticleActivity.this, ArticleBridge.TAG); + selectedWebView.setDownloadListener(new DownloadListenerS(this)); + selectedWebView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View arg0, MotionEvent arg1) { + downX = (int) arg1.getX(); + downY = (int) arg1.getY(); + return false; + } + }); -// if (WithPref.i().getThemeMode() == App.Theme_Day) { -// selectedWebView.getFastScrollDelegate().setThumbDrawable(ContextCompat.getDrawable(App.i(), R.drawable.scrollbar_light)); -// } else { -// selectedWebView.getFastScrollDelegate().setThumbDrawable(ContextCompat.getDrawable(App.i(), R.drawable.scrollbar_dark)); -// } -// selectedWebView.getFastScrollDelegate().setThumbDynamicHeight(false); -// selectedWebView.getFastScrollDelegate().setThumbSize(10, 32); + // 作者:Wing_Li,链接:https://www.jianshu.com/p/3fcf8ba18d7f + selectedWebView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View webView) { + WebView.HitTestResult result = ((WebView) webView).getHitTestResult(); + if (null == result) { + return false; + } + int type = result.getType(); + if (type == WebView.HitTestResult.UNKNOWN_TYPE) { + return false; + } + + // 这里可以拦截很多类型,我们只处理超链接就可以了 + new LongClickPopWindow(ArticleActivity.this, (WebView) webView, ScreenUtil.dp2px(ArticleActivity.this, 120), ScreenUtil.dp2px(ArticleActivity.this, 130), downX, downY + 10); +// webViewLongClickedPopWindow.showAtLocation(webView, Gravity.TOP|Gravity.LEFT, downX, downY + 10); + return true; + } + }); } // 检查该订阅源默认显示什么。【RSS,已读,保存的网页,原始网页】 -// KLog.e("要加载的位置为:" + position + " " + selectedArticle.getTitle()); - - Feed feed = WithDB.i().getFeed(selectedArticle.getOriginStreamId()); - + // KLog.e("要加载的位置为:" + position + " " + selectedArticle.getTitle()); + Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), selectedArticle.getFeedId()); if (feed != null) { toolbar.setTitle(feed.getTitle()); - if (Api.DISPLAY_LINK.equals(GlobalConfig.i().getDisplayMode(feed.getId()))) { - selectedWebView.loadUrl(selectedArticle.getCanonical()); - } else if (Api.DISPLAY_READABILITY.equals(GlobalConfig.i().getDisplayMode(feed.getId()))) { - onReadabilityClick(); + if (App.DISPLAY_LINK.equals(TestConfig.i().getDisplayMode(feed.getId()))) { + selectedWebView.loadUrl(selectedArticle.getLink()); + // 判断是要在加载的时候获取还是同步的时候获取 } else { // KLog.e("加载文章:" + selectedArticle.getTitle()); - selectedWebView.loadData(StringUtil.getPageForDisplay(selectedArticle)); + selectedWebView.loadData(ArticleUtil.getPageForDisplay(selectedArticle)); } } else { - toolbar.setTitle(selectedArticle.getOriginTitle()); - selectedWebView.loadData(StringUtil.getPageForDisplay(selectedArticle)); + selectedWebView.loadData(ArticleUtil.getPageForDisplay(selectedArticle)); } selectedWebView.requestFocus(); } - private class WebChromeClientX extends WebChromeClient { + private static class WebChromeClientX extends WebChromeClient { VideoImpl video; + WeakReference slowlyProgressBar; - WebChromeClientX(VideoImpl video) { + WebChromeClientX(VideoImpl video, WeakReference progressBar) { this.video = video; + this.slowlyProgressBar = progressBar; } @Override public void onProgressChanged(WebView webView, int progress) { // 增加Javascript异常监控,不能增加,会造成页面卡死 // CrashReport.setJavascriptMonitor(webView, true); - if (slowlyProgressBar != null) { - slowlyProgressBar.onProgressChange(progress); + if (slowlyProgressBar.get() != null) { + slowlyProgressBar.get().onProgressChange(progress); } } @@ -535,137 +806,37 @@ public void onHideCustomView() { } } - - @JavascriptInterface - @Override - public void downImage(String articleId, final int index, final String originalUrl) { - final String articleIdInMD5 = StringUtil.str2MD5(articleId); -// final String filePath = App.externalFilesDir + "/cache/" + articleIdInMD5 + "/" + articleIdInMD5 + "_files/"; - final String originalFileDir = App.externalFilesDir + "/cache/" + articleIdInMD5 + "/original/"; - final String fileNameExt = index + "-" + StringUtil.getFileNameExtByUrl(originalUrl); - - if (new File(originalFileDir + fileNameExt + Api.EXT_TMP).exists()) { - return; + private class WebViewClientX extends WebViewClient { + // 通过重写WebViewClient的onReceivedSslError方法来接受所有网站的证书,忽略SSL错误。 + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + KLog.e("SSL错误"); + handler.proceed(); // 忽略SSL证书错误,继续加载页面 } - final String compressedFileDir = App.externalFilesDir + "/cache/" + articleIdInMD5 + "/compressed/"; -// if( !new File(compressedFileDir).getParentFile().exists()){ -// new File(compressedFileDir).getParentFile().mkdirs(); -// } - - FileCallback fileCallback = new FileCallback(originalFileDir, fileNameExt + Api.EXT_TMP) { - @Override - public void onSuccess(Response response) { - new File(originalFileDir + fileNameExt + Api.EXT_TMP).renameTo(new File(originalFileDir + fileNameExt)); - -// AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { -// @Override -// public void run() { - Luban.with(App.i()) - .load(originalFileDir + fileNameExt) - .ignoreBy(256) // 忽略512kb以下的文件 - // 缓存压缩图片路径 -// .setTargetPath(compressedFileDir + fileNameExt) - .setTargetDir(compressedFileDir) - // 设置开启压缩条件。当路径为空或者为gif时,不压缩 - .setRenameListener(new OnRenameListener() { - @Override - public String rename(String filePath) { - return fileNameExt; - } - }) - .filter(new CompressionPredicate() { - @Override - public boolean apply(String preCompressedPath) { - KLog.e("压缩图片,忽略压缩:" + preCompressedPath); - return !(TextUtils.isEmpty(preCompressedPath) || preCompressedPath.toLowerCase().endsWith(".gif")); - } - }) - .setCompressListener(new OnCompressListener() { - // TODO 压缩开始前调用,可以在方法内启动 loading UI - @Override - public void onStart() { - } - - @Override - public void onSuccess(final File file) { - if (selectedWebView == null) { - return; - } -// WindowManager manager = ArticleActivity.this.getWindowManager(); -// DisplayMetrics outMetrics = new DisplayMetrics(); -// manager.getDefaultDisplay().getMetrics(outMetrics); -// KLog.e("【宽度】2:",outMetrics.widthPixels + " " + outMetrics.heightPixels); - KLog.e("加载压缩图片成功1:" + Thread.currentThread() + " " + file.getPath() + " " + compressedFileDir); - selectedWebView.loadUrl("javascript:onImageLoadSuccess('" + originalUrl + "','" + file.getPath() + "')"); - } - - @Override - public void onError(Throwable e) { - if (selectedWebView == null) { - return; - } - KLog.e("压缩图片报错"); - selectedWebView.loadUrl("javascript:onImageLoadSuccess('" + originalUrl + "','" + originalFileDir + fileNameExt + "')"); -// selectedWebView.post(new Runnable() { -// @Override -// public void run() { -// } -// }); - } - }).launch(); -// } -// }); - -// selectedWebView.loadUrl("javascript:onImageLoadSuccess('" + originalUrl + "','" + filePath + fileNameExt + "')"); - KLog.e("下载图片成功,准备加载" + originalUrl + "','" + originalFileDir + fileNameExt); - } - - // 该方法执行在主线程中 - @Override - public void onError(Response response) { - if (selectedWebView != null) { - selectedWebView.loadUrl("javascript:onImageLoadFailed('" + originalUrl + "')"); - } - new File(originalFileDir + fileNameExt + Api.EXT_TMP).delete(); - } - }; - Request request = OkGo.get(originalUrl) - .tag(articleId) - .client(articleHttpClient); - -// String referer = GlobalConfig.i().guessRefererByUrl(originalUrl); -//// KLog.e("图片链接是:" + originalUrl + ", 来源是:" + referer); -// if (!TextUtils.isEmpty(referer)) { -// request.headers(Api.Referer, referer); -// }else { -// request.headers(Api.Referer, selectedArticle.getCanonical()); -// } - request.headers(Api.Referer, selectedArticle.getCanonical()); - request.execute(fileCallback); -// KLog.e("下载:" + originalUrl + " 来源 " + selectedArticle.getCanonical() ); - } - private class WebViewClientX extends WebViewClient { @Deprecated @SuppressLint("NewApi") @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - if (GlobalConfig.i().isBlockAD() && AdBlock.i().isAd(url)) { + public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) { + //KLog.e("【请求加载资源】" + url); + if ( AdBlock.i().isAd(request.getUrl().toString()) ) { // 有广告的请求数据,我们直接返回空数据,注:不能直接返回null return new WebResourceResponse(null, null, null); } - return super.shouldInterceptRequest(view, url); + return super.shouldInterceptRequest(view, request); } /** * @param webView * @param url - * @return 返回 true 表示你已经处理此次请求。 + * @return + * 返回 true 表示你已经处理此次请求。 * 返回 false 表示由webview自行处理(一般都是把此url加载出来)。 * 返回super.shouldOverrideUrlLoading(view, url); 这个返回的方法会调用父类方法,也就是跳转至手机浏览器 */ @Override public boolean shouldOverrideUrlLoading(WebView webView, String url) { + KLog.e("url为:" + url); // 判断重定向的方式一 // 作者:胡几手,链接:https://www.jianshu.com/p/7dfb8797f893 // 解决在webView第一次加载的url重定向到了另一个地址时,也会走shouldOverrideUrlLoading回调的问题 @@ -675,62 +846,29 @@ public boolean shouldOverrideUrlLoading(WebView webView, String url) { } else if (hitTestResult.getType() == WebView.HitTestResult.UNKNOWN_TYPE) { return false; } - //http和https协议开头的执行正常的流程 - if (url.startsWith("http") || url.startsWith("https")) { - openLink(url); - OkGo.cancelAll(articleHttpClient); + + if (TextUtils.isEmpty(url) || url.startsWith(SCHEMA_FILE)) { return true; } - /** - * 【scheme链接打开本地应用】(https://www.jianshu.com/p/45af72036e58) - */ - //其他的URL则会开启一个Acitity然后去调用原生APP - final Intent in = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - if (in.resolveActivity(getPackageManager()) == null) { - // TODO: 2018/4/25 说明系统中不存在这个activity。弹出一个Toast提示是否要用外部应用打开 - return true; + String newUrl = LinkRewriteConfig.i().getRedirectUrl( url ); + if (!TextUtils.isEmpty(newUrl)) { + // 创建一个新请求,并相应地修改它 + url = newUrl; } - in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - String name = "相应的"; - try { - name = "" + getPackageManager().getApplicationLabel(getPackageManager().getApplicationInfo(in.resolveActivity(getPackageManager()).getPackageName(), PackageManager.GET_META_DATA)); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - - new MaterialDialog.Builder(ArticleActivity.this) - .content("是否跳转到「" + name + "」应用?") - .negativeText("取消") - .positiveText(R.string.agree) - .onPositive(new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - startActivity(in); - overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); - } - }) - .show(); - - + openLink(url); return true; } @Override public void onPageStarted(WebView webView, String url, Bitmap favicon) { super.onPageStarted(webView, url, favicon); - canSlide = false; + //KLog.e("页面加载开始"); if (slowlyProgressBar != null) { slowlyProgressBar.onProgressStart(); } - Message m = Message.obtain(); - // 超时 - m.what = 0; - // 5秒后如果没有加载完毕,则停止加载 - articleHandler.sendMessageDelayed(m, 10000); } - /** * 不能直接在这里就初始化setupImage,因为在viewpager中预加载而生成webview的时候,这里的懒加载就被触发了 * webView.loadUrl("javascript:setTimeout(\"setupImage()\",100)"); @@ -738,497 +876,588 @@ public void onPageStarted(WebView webView, String url, Bitmap favicon) { @Override public void onPageFinished(WebView webView, String url) { super.onPageFinished(webView, url); + webView.getSettings().setBlockNetworkImage(false); Integer process = App.i().articleProgress.get(articleId); -// KLog.e("页面加载完成:" + selectedArticle.getTitle() + " " + articleId + " " + process); - if (process != null) { + // KLog.e("页面加载完成:" + selectedArticle.getTitle() + " " + articleId + " " + process); + if (process != null && selectedWebView != null) { selectedWebView.scrollTo(0, process); } - canSlide = true; } } - - private void initIconState(int position) { - if (selectedArticle.getReadStatus() == Api.UNREAD) { + private void initIconState() { + if (selectedArticle.getReadStatus() == App.STATUS_UNREAD) { readView.setText(getString(R.string.font_readed)); - DataApi.i().markArticleReaded(articleHttpClient, selectedArticle.getId(), null); - // 方法2 - WithDB.i().setReaded(selectedArticle); - } else if (selectedArticle.getReadStatus() == Api.READED) { + selectedArticle.setReadStatus(App.STATUS_READED); + CoreDB.i().articleDao().update(selectedArticle); + App.i().getApi().markArticleReaded(selectedArticle.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { } + + @Override + public void onFailure(Object error) { + selectedArticle.setReadStatus(App.STATUS_UNREAD); + CoreDB.i().articleDao().update(selectedArticle); + ToastUtils.show(getString(R.string.mask_fail)); + } + }); + } else if (selectedArticle.getReadStatus() == App.STATUS_READED) { readView.setText(getString(R.string.font_readed)); - } else if (selectedArticle.getReadStatus() == Api.UNREADING) { + } else if (selectedArticle.getReadStatus() == App.STATUS_UNREADING) { readView.setText(getString(R.string.font_unread)); } - if (selectedArticle.getStarStatus() == Api.UNSTAR) { + if (selectedArticle.getStarStatus() == App.STATUS_UNSTAR) { starView.setText(getString(R.string.font_unstar)); } else { starView.setText(getString(R.string.font_stared)); } - if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_CACHE)) { + if (App.STATUS_NOT_FILED == selectedArticle.getSaveStatus()) { saveView.setText(getString(R.string.font_unsave)); } else { saveView.setText(getString(R.string.font_saved)); } - -// articleNumView.setText((position + 1) + " / " + articleCount); -// KLog.i("=====position" + position); } public void onReadClick(View view) { // KLog.e("loread", "被点击的是:" + selectedArticle.getTitle()); - if (selectedArticle.getReadStatus() == Api.READED) { + if (selectedArticle.getReadStatus() == App.STATUS_READED) { readView.setText(getString(R.string.font_unread)); - DataApi.i().markArticleUnread(selectedArticle.getId(), null); - WithDB.i().setUnreading(selectedArticle); + selectedArticle.setReadStatus(App.STATUS_UNREADING); + CoreDB.i().articleDao().update(selectedArticle); + App.i().getApi().markArticleUnread(selectedArticle.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + selectedArticle.setReadStatus(App.STATUS_READED); + CoreDB.i().articleDao().update(selectedArticle); + ToastUtils.show(getString(R.string.mask_fail)); + } + }); } else { readView.setText(getString(R.string.font_readed)); - DataApi.i().markArticleReaded(selectedArticle.getId(), null); - WithDB.i().setReaded(selectedArticle); + selectedArticle.setReadStatus(App.STATUS_READED); + CoreDB.i().articleDao().update(selectedArticle); + + App.i().getApi().markArticleReaded(selectedArticle.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + selectedArticle.setReadStatus(App.STATUS_UNREAD); + CoreDB.i().articleDao().update(selectedArticle); + ToastUtils.show(getString(R.string.mask_fail)); + } + }); + } } - public void onStarClick(View view) { - if (selectedArticle.getStarStatus() == Api.UNSTAR) { + public void onClickStarIcon(View view) { + String uid = App.i().getUser().getId(); + if (selectedArticle.getStarStatus() == App.STATUS_UNSTAR) { starView.setText(getString(R.string.font_stared)); - DataApi.i().markArticleStared(selectedArticle.getId(), null); - selectedArticle.setStarStatus(Api.STARED); - selectedArticle.setStarred(System.currentTimeMillis() / 1000); - if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_BOX)) { - selectedArticle.setSaveDir(Api.SAVE_DIR_STORE); + selectedArticle.setStarStatus(App.STATUS_STARED); + CoreDB.i().articleDao().update(selectedArticle); + App.i().getApi().markArticleStared(selectedArticle.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + selectedArticle.setStarStatus(App.STATUS_UNSTAR); + CoreDB.i().articleDao().update(selectedArticle); + ToastUtils.show(getString(R.string.mask_fail)); + } + }); + + List categories = CoreDB.i().categoryDao().getByFeedId(uid,selectedArticle.getFeedId()); + String msg = null; + String action = null; + if(categories == null || StringUtils.isEmpty(categories)){ + msg = getString(R.string.star_marked); + action = getString(R.string.add_to_favorites); + }else if(categories.size() == 1){ + msg = getString(R.string.star_marked_to_favorites,categories.get(0).getTitle()); + action = getString(R.string.edit_favorites); + + Tag tag = new Tag(); + tag.setUid(uid); + tag.setId(categories.get(0).getTitle()); + tag.setTitle(categories.get(0).getTitle()); + CoreDB.i().tagDao().insert(tag); + ArticleTag articleTag = new ArticleTag(uid,selectedArticle.getId(),tag.getId()); + CoreDB.i().articleTagDao().insert(articleTag); + }else { + msg = getString(R.string.star_marked_to_favorites,categories.get(0).getTitle() + getString(R.string.etc)); + action = getString(R.string.edit_favorites); + + Tag tag = new Tag(); + tag.setUid(uid); + tag.setId(categories.get(0).getTitle()); + //tag.setTitle(categories.get(0).getTitle()); + CoreDB.i().tagDao().insert(tag); + ArticleTag articleTag = new ArticleTag(uid,selectedArticle.getId(),tag.getId()); + CoreDB.i().articleTagDao().insert(articleTag); } + + SnackbarUtil.Long(swipeRefreshLayoutS, bottomBar, msg).setAction(action, v -> editFavorites(uid)).show(); } else { starView.setText(getString(R.string.font_unstar)); - DataApi.i().markArticleUnstar(selectedArticle.getId(), null); - selectedArticle.setStarStatus(Api.UNSTAR); - if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_STORE)) { - selectedArticle.setSaveDir(Api.SAVE_DIR_BOX); - } + selectedArticle.setStarStatus(App.STATUS_UNSTAR); + CoreDB.i().articleDao().update(selectedArticle); + App.i().getApi().markArticleUnstar(selectedArticle.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + selectedArticle.setStarStatus(App.STATUS_STARED); + CoreDB.i().articleDao().update(selectedArticle); + ToastUtils.show(getString(R.string.mask_fail)); + } + }); + CoreDB.i().articleTagDao().deleteByArticleId(uid,selectedArticle.getId()); + ArticleTags.i().removeArticle(selectedArticle.getId()); + ArticleTags.i().save(); } - WithDB.i().saveArticle(selectedArticle); } - public void onSaveClick(View view) { -// KLog.e("loread", "保存文件被点击"); - if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_CACHE)) { - if (selectedArticle.getStarStatus() == Api.STARED) { - selectedArticle.setSaveDir(Api.SAVE_DIR_STORE); - } else { - selectedArticle.setSaveDir(Api.SAVE_DIR_BOX); + public void editFavorites(String uid){ + // 找出当前用户有的所有tags + List tags = CoreDB.i().tagDao().getAll(uid); + // 找出当前用户该文章的tags + List originalArticleTags = CoreDB.i().articleTagDao().getByArticleId(uid, selectedArticle.getId()); + + Integer[] preSelectedIndices = null; + int index = 0; + if(originalArticleTags!=null && originalArticleTags.size() > 0){ + preSelectedIndices = new Integer[]{originalArticleTags.size()}; + } + String[] tagTitles; + if( tags != null ){ + tagTitles = new String[tags.size()]; + for (int i = 0, size = tags.size(); i < size; i++) { + String title = tags.get(i).getTitle(); + tagTitles[i] = title; + //KLog.e("标题ss:" + title + " , " + originalArticleTags + " " + index + " " + i ); + if(preSelectedIndices!=null){ + for (ArticleTag articleTag:originalArticleTags) { + //KLog.e("标题:" + title + " , " + articleTag.getTagId() + " " + index + " " + i ); + if(title.equals(articleTag.getTagId())){ + preSelectedIndices[index] = i; + index ++; + } + } + } } + + ArrayList selectedArticleTags = new ArrayList<>(); + new MaterialDialog.Builder(ArticleActivity.this) + .title(getString(R.string.select_tag)) + .items(tagTitles) + .itemsCallbackMultiChoice(preSelectedIndices, (dialog, which, text) -> { + selectedArticleTags.clear(); + ArticleTag articleTag; + for (int i : which) { + articleTag = new ArticleTag(uid, selectedArticle.getId(), tags.get(i).getId() ); + selectedArticleTags.add(articleTag); + } + KLog.e("已选择收藏夹:" + Arrays.toString(text)); + return true; + }) + .positiveText(R.string.confirm) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + if(selectedArticleTags.size() > 0){ + CoreDB.i().articleTagDao().deleteByArticleId(selectedArticle.getUid(),selectedArticle.getId()); + CoreDB.i().articleTagDao().insert(selectedArticleTags); + ArticleTags.i().removeArticle(selectedArticle.getId()); + ArticleTags.i().addArticleTags(selectedArticleTags); + ArticleTags.i().save(); + } + dialog.dismiss(); + } + }) + .neutralText(getString(R.string.new_favorites)) + .onNeutral(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + newFavorites(uid,dialog); + } + }) + .alwaysCallMultiChoiceCallback() // the callback will always be called, to check if selection is still allowed + .show(); + }else { + newFavorites(uid,null); + } + } + public void newFavorites(String uid,@Nullable MaterialDialog lastDialog){ + new MaterialDialog.Builder(ArticleActivity.this) + .title(R.string.new_favorites) + .inputType(InputType.TYPE_CLASS_TEXT) + .inputRange(1, 16) + .input(getString(R.string.new_favorites), null, new MaterialDialog.InputCallback() { + @Override + public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { + Tag tag = new Tag(); + tag.setUid(uid); + tag.setId(input.toString()); + tag.setTitle(input.toString()); + CoreDB.i().tagDao().insert(tag); + dialog.dismiss(); + if(lastDialog != null){ + lastDialog.dismiss(); + } + editFavorites(uid); + KLog.e("正在新建收藏夹:" + input.toString()); + } + }) + .positiveText(R.string.confirm) + .negativeText(android.R.string.cancel) + .show(); + } + + + +// // 找出当前用户有的所有tags +// List tags = CoreDB.i().tagDao().getAll(uid); +// // 找出当前用户该文章的tags +// List originalArticleTags = CoreDB.i().articleTagDao().getByArticleId(uid, articleId); +// +// String[] tagTitles = new String[0]; +// Integer[] preSelectedIndices = new Integer[]{0}; +// if( tags != null ){ +// tagTitles = new String[tags.size()]; +// +// HashSet tagIdSet = new HashSet(); +// if(originalArticleTags!=null){ +// preSelectedIndices = new Integer[]{originalArticleTags.size()}; +// for (int i = 0, size = originalArticleTags.size(); i < size; i++) { +// tagIdSet.add(originalArticleTags.get(i).getTagId()); +// } +// } +// +// int index = 0; +// for (int i = 0, size = tags.size(); i < size; i++) { +// String title = tags.get(i).getTitle(); +// tagTitles[i] = title; +// if(tagIdSet.contains(title)){ +// preSelectedIndices[index] = i; +// index++; +// } +// } +// +// new MaterialDialog.Builder(ArticleActivity.this) +// .title(getString(R.string.select_tag)) +// .items(tagTitles) +// .itemsCallbackMultiChoice(preSelectedIndices, (dialog, which, text) -> { +// final ArrayList selectedArticleTags = new ArrayList<>(); +// ArticleTag articleTag; +// for (int i : which) { +// articleTag = new ArticleTag(uid, articleId, tags.get(i).getId() ); +// selectedArticleTags.add(articleTag); +// } +// CoreDB.i().articleTagDao().delete(originalArticleTags); +// CoreDB.i().articleTagDao().insert(selectedArticleTags); +// return true; +// }) +// .alwaysCallMultiChoiceCallback() // the callback will always be called, to check if selection is still allowed +// .show(); +// } + + + public void onClickSaveIcon(View view) { + if (selectedArticle.getSaveStatus() == App.STATUS_NOT_FILED) { saveView.setText(getString(R.string.font_saved)); - } else { - selectedArticle.setSaveDir(Api.SAVE_DIR_CACHE); + selectedArticle.setSaveStatus(App.STATUS_TO_BE_FILED); + addToSaveDirectory(App.i().getUser().getId()); + } else if (selectedArticle.getSaveStatus() == App.STATUS_TO_BE_FILED){ saveView.setText(getString(R.string.font_unsave)); + selectedArticle.setSaveStatus(App.STATUS_NOT_FILED); + SaveDirectory.i().setArticleDirectory(selectedArticle.getId(),null); + } else if (selectedArticle.getSaveStatus() == App.STATUS_IS_FILED){ + saveView.setText(getString(R.string.font_saved)); + ToastUtils.show(getString(R.string.is_filed_cannot_edit)); } - WithDB.i().saveArticle(selectedArticle); + CoreDB.i().articleDao().update(selectedArticle); } - public void clickReadability(View view) { - onReadabilityClick(); + public void clearDirectory(String uid){ + SaveDirectory.i().setArticleDirectory(selectedArticle.getId(),null); } - - public void onReadabilityClick() { - saveArticleProgress(); - if (selectedWebView.isReadability()) { - ToastUtil.showLong(getString(R.string.toast_cancel_readability)); - selectedWebView.loadData(StringUtil.getPageForDisplay(selectedArticle)); - selectedWebView.setReadability(false); - return; + public void addToSaveDirectory(String uid){ + String dir = SaveDirectory.i().getSaveDir(selectedArticle.getFeedId(),selectedArticle.getId()); + String msg; + if (StringUtils.isEmpty(dir)) { + msg = getString(R.string.saved_to_root_directory); + }else { + msg = getString(R.string.saved_to_directory,dir); } - swipeRefreshLayoutS.setRefreshing(true); - ToastUtil.showLong(getString(R.string.toast_get_readability_ing)); - final Handler handler = new Handler(Looper.getMainLooper()); - OkGo.get(selectedArticle.getCanonical()).client(articleHttpClient).getRawCall().enqueue(new okhttp3.Callback() { - @Override - public void onFailure(Call call, IOException e) { - handler.post(new Runnable() { + SnackbarUtil.Long(swipeRefreshLayoutS, bottomBar, msg) + .setAction(R.string.edit_directory, v -> editDirectory(uid)).show(); + } + + public void editDirectory(String uid){ + String[] savedFoldersTitle = SaveDirectory.i().getDirectoriesOptionName(); + List savedFoldersValue = SaveDirectory.i().getDirectoriesOptionValue(); + new MaterialDialog.Builder(this) + .title(getString(R.string.edit_directory)) + .items(savedFoldersTitle) + .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() { @Override - public void run() { - swipeRefreshLayoutS.setRefreshing(false); - ToastUtil.showLong(getString(R.string.toast_get_readability_failure)); + public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { + SaveDirectory.i().setArticleDirectory(selectedArticle.getId(),savedFoldersValue.get(which)); + SaveDirectory.i().save(); + KLog.e("被选择的目录为:" + text.toString()); + return true; } - }); - } + }) +// .neutralText(getString(R.string.new_directory)) +// .onNeutral(new MaterialDialog.SingleButtonCallback() { +// @Override +// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { +// newDirectory(uid,dialog); +// } +// }) + .show(); + } +// +// public void newDirectory(String uid,@Nullable MaterialDialog lastDialog){ +// new MaterialDialog.Builder(this) +// .title(R.string.new_directory) +// .inputType(InputType.TYPE_CLASS_TEXT) +// .inputRange(1, 16) +// .input(getString(R.string.new_directory), null, new MaterialDialog.InputCallback() { +// @Override +// public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { +// SaveDirectory.i().newDirectory(input.toString()); +// SaveDirectory.i().save(); +// if(lastDialog != null){ +// lastDialog.dismiss(); +// } +// editDirectory(uid); +// KLog.e("正在新建收藏夹:" + input.toString()); +// } +// }) +// .positiveText(R.string.confirm) +// .negativeText(android.R.string.cancel) +// .show(); +// } - // 在Android应用中直接使用上述代码进行异步请求,并且在回调方法中操作了UI,那么你的程序就会抛出异常,并且告诉你不能在非UI线程中操作UI。 - // 这是因为OkHttp对于异步的处理仅仅是开启了一个线程,并且在线程中处理响应。 - // OkHttp是一个面向于Java应用而不是特定平台(Android)的框架,那么它就无法在其中使用Android独有的Handler机制。 - @Override - public void onResponse(Call call, okhttp3.Response response) throws IOException { - if (response.isSuccessful()) { - Document doc = Jsoup.parse(response.body().byteStream(), DataUtil.getCharsetFromContentType(response.body().contentType().toString()), selectedArticle.getCanonical()); - final String content = Extractor.getContent(selectedArticle.getCanonical(), doc); + public void clickOpenOriginalArticle(View view) { + Intent intent = new Intent(ArticleActivity.this, WebActivity.class); + intent.setData(Uri.parse(selectedArticle.getLink())); + intent.putExtra("theme", App.i().getUser().getThemeMode()); + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } - handler.post(new Runnable() { + private Article optimizedArticle; + public void onReadabilityClick(View view) { + if(swipeRefreshLayoutS.isRefreshing()){ + OkGo.cancelTag(HttpClientManager.i().simpleClient(),"Readability"); + swipeRefreshLayoutS.setRefreshing(false); + return; + } + saveArticleProgress(); + if(optimizedArticle != null){ + ToastUtils.show(getString(R.string.cancel_readability)); + selectedWebView.loadData(ArticleUtil.getPageForDisplay(selectedArticle)); + CoreDB.i().articleDao().update(selectedArticle); + ((IconFontView)view).setText(getString(R.string.font_article_original)); + optimizedArticle = null; + }else { + ToastUtils.show(getString(R.string.get_readability_ing)); + swipeRefreshLayoutS.setRefreshing(true); + + okhttp3.Request request = new okhttp3.Request.Builder().url(selectedArticle.getLink()).tag("Readability").build(); + Call call = HttpClientManager.i().simpleClient().newCall(request); + call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + articleHandler.post(new Runnable() { @Override public void run() { - selectedWebView.loadData(StringUtil.getPageForDisplay(selectedArticle, content, Api.DISPLAY_READABILITY)); - KLog.e("获取到的内容:" + content); - ToastUtil.showLong(getString(R.string.toast_get_readability_success)); - selectedWebView.setReadability(true); - - if (BuildConfig.DEBUG) { - SnackbarUtil.Long(swipeRefreshLayoutS, "保存 Readability 内容?") - .setAction("同意", new View.OnClickListener() { - @Override - public void onClick(View v) { - selectedArticle.setContent(content); - WithDB.i().updateArticle(selectedArticle); - } - }).show(); + if (swipeRefreshLayoutS == null) { + return; } + swipeRefreshLayoutS.setRefreshing(false); + ToastUtils.show(getString(R.string.get_readability_failure)); } }); } - handler.post(new Runnable() { - @Override - public void run() { - swipeRefreshLayoutS.setRefreshing(false); - } - }); - } - }); - } - - private String selectedFeedDisplayMode = Api.DISPLAY_RSS; - private EditText feedNameEdit; - private void initFeedConfig() { - final Feed feed = WithDB.i().getFeed(selectedArticle.getOriginStreamId()); - final View feedConfigView = findViewById(R.id.article_feed_config); - if (feed != null) { - feedConfigView.setVisibility(View.VISIBLE); - feedConfigView.setOnClickListener(new View.OnClickListener() { + // 这是因为OkHttp对于异步的处理仅仅是开启了一个线程,并且在线程中处理响应,所以不能再其中操作UI。 + // OkHttp是一个面向于Java应用而不是特定平台(Android)的框架,那么它就无法在其中使用Android独有的Handler机制。 @Override - public void onClick(View view) { - showConfigFeedDialog(feed, feedConfigView); + public void onResponse(@NotNull Call call, @NotNull okhttp3.Response response) throws IOException { + optimizedArticle = ArticleUtil.getReadabilityArticle(selectedArticle,response.body()); + CoreDB.i().articleDao().update(optimizedArticle); + articleHandler.post(new Runnable() { + @Override + public void run() { + if (swipeRefreshLayoutS == null ||selectedWebView == null) { + return; + } + swipeRefreshLayoutS.setRefreshing(false); + ToastUtils.show(getString(R.string.get_readability_success)); + ((IconFontView)view).setText(getString(R.string.font_article_readability)); + selectedWebView.loadData(ArticleUtil.getPageForDisplay(optimizedArticle)); + } + }); } }); - } else { - feedConfigView.setVisibility(View.GONE); } } - public void showConfigFeedDialog(final Feed feed, final View feedConfigView) { - if (feed == null) { + public void onReadabilityClick() { + saveArticleProgress(); + if (selectedWebView.isReadability()) { + ToastUtils.show(getString(R.string.cancel_readability)); + selectedWebView.loadData(ArticleUtil.getPageForDisplay(selectedArticle)); + selectedWebView.setReadability(false); return; } - final MaterialDialog feedConfigDialog = new MaterialDialog.Builder(this) - .title("配置该源") - .customView(R.layout.config_feed_view, true) - .positiveText("确认") - .negativeText("取消") - .neutralText("退订") - .neutralColor(Color.RED) - .onNeutral(new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - DataApi.i().unsubscribeFeed(articleHttpClient, feed.getId(), new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; - } - WithDB.i().unsubscribeFeed(feed); - ToastUtil.showLong("退订成功"); - feedConfigView.setVisibility(View.GONE); - // 返回 mainActivity 页面,并且跳到下一个 tag/feed - // KLog.e("移除" + itemView.groupPos + " " + itemView.childPos ); - } + swipeRefreshLayoutS.setRefreshing(true); - @Override - public void onError(Response response) { - ToastUtil.showLong(App.i().getString(R.string.toast_unsubscribe_fail)); - } - }); + ToastUtils.show(getString(R.string.get_readability_ing)); - } - }) - .onPositive(new MaterialDialog.SingleButtonCallback() { + okhttp3.Request request = new okhttp3.Request.Builder().url(selectedArticle.getLink()).build(); + Call call = HttpClientManager.i().simpleClient().newCall(request); + call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + articleHandler.post(new Runnable() { @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -// KLog.e("显示模式:" + selectedFeedDisplayMode); - Feed feedx = feed; - renameFeed(feedNameEdit.getText().toString(), feedx); - feedx.update(); - - if (!selectedFeedDisplayMode.equals(Api.DISPLAY_RSS)) { - GlobalConfig.i().addDisplayRouter(feed.getId(), selectedFeedDisplayMode); - } else { - GlobalConfig.i().removeDisplayRouter(feed.getId()); + public void run() { + if (swipeRefreshLayoutS != null) { + swipeRefreshLayoutS.setRefreshing(false); + ToastUtils.show(getString(R.string.get_readability_failure)); } - GlobalConfig.i().save(); - - dialog.dismiss(); } - }).build(); - - feedConfigDialog.show(); - feedNameEdit = (EditText) feedConfigDialog.findViewById(R.id.feed_name_edit); - feedNameEdit.setText(feed.getTitle()); - - TextView feedLink = (TextView) feedConfigDialog.findViewById(R.id.feed_link); - feedLink.setText(feed.getUrl()); - feedLink.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - //获取剪贴板管理器: - ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - // 创建普通字符型ClipData - ClipData mClipData = ClipData.newPlainText("RSS Link", feed.getUrl()); - // 将ClipData内容放到系统剪贴板里。 - cm.setPrimaryClip(mClipData); - ToastUtil.showShort("复制成功!"); - } - }); - - - TextView feedOpenModeSelect = (TextView) feedConfigDialog.findViewById(R.id.feed_open_mode_select); - if (TextUtils.isEmpty(GlobalConfig.i().getDisplayMode(feed.getId()))) { - selectedFeedDisplayMode = Api.DISPLAY_RSS; - } else { - selectedFeedDisplayMode = GlobalConfig.i().getDisplayMode(feed.getId()); - } - feedOpenModeSelect.setText(selectedFeedDisplayMode); - feedOpenModeSelect.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - showDisplayModePopupMenu(view); + }); } - }); - TextView feedCategoryView = (TextView) feedConfigDialog.findViewById(R.id.feed_tag_category); - if (!TextUtils.isEmpty(feed.getCategoryid())) { - feedCategoryView.setText(feed.getCategorylabel()); - } else { - feedCategoryView.setText(getString(R.string.main_activity_title_untag)); - } - feedCategoryView.setOnClickListener(new View.OnClickListener() { + // 这是因为OkHttp对于异步的处理仅仅是开启了一个线程,并且在线程中处理响应,所以不能再其中操作UI。 + // OkHttp是一个面向于Java应用而不是特定平台(Android)的框架,那么它就无法在其中使用Android独有的Handler机制。 @Override - public void onClick(View view) { - feedConfigDialog.dismiss(); - final ArrayList tags = WithDB.i().getTagsWithUnreadCount(); - final ArrayList titles = new ArrayList<>(tags.size()); - int selectedIndex = -1; - - for (int i = 0, size = tags.size(); i < size; i++) { - titles.add(tags.get(i).getTitle()); - if (tags.get(i).getId().equals(feed.getCategoryid())) { - selectedIndex = i; + public void onResponse(@NotNull Call call, @NotNull okhttp3.Response response) throws IOException { + if (!response.isSuccessful()) { + if (swipeRefreshLayoutS != null) { + swipeRefreshLayoutS.setRefreshing(false); } + return; } - final Integer[] preSelectedIndices; - if (selectedIndex != -1) { - preSelectedIndices = new Integer[]{selectedIndex}; - } else { - preSelectedIndices = null; - } + Article optimizedArticle = ArticleUtil.getReadabilityArticle(selectedArticle,response.body()); - new MaterialDialog.Builder(ArticleActivity.this) - .title("修改分组") - .items(titles) - .itemsCallbackMultiChoice(preSelectedIndices, new MaterialDialog.ListCallbackMultiChoice() { - @Override - public boolean onSelection(MaterialDialog dialog, final Integer[] which, CharSequence[] text) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("ac", "edit"); - builder.add("s", feed.getId()); - if (which.length == 0) { - builder.add("r", feed.getCategoryid()); - } else { - builder.add("a", tags.get(which[0]).getId()); - } - DataApi.i().editFeed(builder, new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; - } + articleHandler.post(new Runnable() { + @Override + public void run() { + if (swipeRefreshLayoutS != null) { + swipeRefreshLayoutS.setRefreshing(false); + } + if (selectedWebView == null) { + return; + } - if (which != null && which.length == 0) { - feed.setCategoryid(""); - feed.setCategorylabel(""); - } else { - feed.setCategoryid(tags.get(which[0]).getId()); - feed.setCategorylabel(tags.get(which[0]).getTitle()); - } - feed.update(); - ToastUtil.showLong("修改分组成功!"); - } + selectedWebView.loadData(ArticleUtil.getPageForDisplay(optimizedArticle)); + ToastUtils.show(getString(R.string.get_readability_success)); + selectedWebView.setReadability(true); + SnackbarUtil.Long(swipeRefreshLayoutS, bottomBar, getString(R.string.save_readability_content)) + .setAction(getString(R.string.agree), new View.OnClickListener() { @Override - public void onError(Response response) { - ToastUtil.showLong(App.i().getString(R.string.toast_rename_fail)); + public void onClick(View v) { + CoreDB.i().articleDao().update(optimizedArticle); } - }); - return which.length <= 1; - } - }) - .alwaysCallMultiChoiceCallback() // the callback will always be called, to check if selection is still allowed - .show(); - } - }); - } - - - public void renameFeed(final String renamedTitle, final Feed feedx) { - KLog.e("=====" + renamedTitle + feedx.getId()); - if (renamedTitle.equals("") || feedx.getTitle().equals(renamedTitle)) { - return; - } - DataApi.i().renameFeed(articleHttpClient, feedx.getId(), renamedTitle, new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; - } - Feed feed = feedx; - feed.setTitle(renamedTitle); - feed.update(); - // 由于改了 feed 的名字,而每个 article 自带的 feed 名字也得改过来。 - WithDB.i().updateArtsFeedTitle(feed); -// KLog.e("改了名字" + renamedTitle ); - } - - @Override - public void onError(Response response) { - ToastUtil.showLong(App.i().getString(R.string.toast_rename_fail)); + }).show(); + } + }); } }); } - public void showDisplayModePopupMenu(final View view) { - PopupMenu popupMenu = new PopupMenu(this, view); - MenuInflater menuInflater = popupMenu.getMenuInflater(); - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - switch (menuItem.getItemId()) { - case R.id.display_mode_rss: - selectedFeedDisplayMode = Api.DISPLAY_RSS; - break; -// case R.id.display_mode_readability: -// selectedFeedDisplayMode = Api.DISPLAY_READABILITY; -// break; - case R.id.display_mode_link: - selectedFeedDisplayMode = Api.DISPLAY_LINK; - break; - default: - selectedFeedDisplayMode = Api.DISPLAY_RSS; - break; - } - KLog.e("选择:" + selectedFeedDisplayMode); - ((TextView) view).setText(menuItem.getTitle()); - return false; + private void initFeedConfig() { + final Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), selectedArticle.getFeedId()); + //final View feedConfigView = findViewById(R.id.article_feed_config); + if( feedMenuItem != null ){ + if (feed != null) { + feedMenuItem.setVisible(true); + }else { + feedMenuItem.setVisible(false); } - }); - // 加载布局文件到菜单中去 - menuInflater.inflate(R.menu.menu_article_display_mode, popupMenu.getMenu()); - popupMenu.show(); + } } -// public void editFeed(final Feed newFeed){ -// final Feed localFeed = WithDB.i().getFeed(newFeed.getId()); -// if( localFeed != null){ -// return; -// } -// -// FormBody.Builder builder = new FormBody.Builder(); -// builder.add("ac", "edit"); // 可省略 -// builder.add("s", newFeed.getId()); -// -// if( !localFeed.getCategoryid().equals(newFeed.getCategoryid()) ){ -// builder.add("r", localFeed.getCategoryid()); -// builder.add("a", newFeed.getCategoryid()); -// } -// -// if( !localFeed.getTitle().equals(newFeed.getTitle()) ){ -// builder.add("t", newFeed.getTitle()); -// } -// -// DataApi.i().editFeed(articleHttpClient,builder,, new StringCallback() { -// @Override -// public void onSuccess(Response response) { -// if (!response.body().equals("OK")) { -// this.onError(response); -// return; -// } -// newFeed.update(); -// // 由于改了 feed 的名字,而每个 article 自带的 feed 名字也得改过来。 -// if( !localFeed.getTitle().equals(newFeed.getTitle()) ){ -// WithDB.i().updateArtsFeedTitle(newFeed); -// } -//// KLog.e("改了名字" + renamedTitle ); -// } -// -// @Override -// public void onError(Response response) { -// ToastUtil.showLong(App.i().getString(R.string.toast_rename_fail)); -// } -// }); -// } - - - public void showArticleInfo(View view) { + public void showArticleInfo() { // KLog.e("文章信息"); if (!BuildConfig.DEBUG) { return; } + Document document = Jsoup.parseBodyFragment(ArticleUtil.getPageForDisplay(selectedArticle)); + document.outputSettings().prettyPrint(true); String info = selectedArticle.getTitle() + "\n" + "ID=" + selectedArticle.getId() + "\n" + - "ID-MD5=" + StringUtil.str2MD5(selectedArticle.getId()) + "\n" + - "ReadState=" + selectedArticle.getReadState() + "\n" + - "StarState=" + selectedArticle.getStarState() + "\n" + - "SaveDir=" + selectedArticle.getSaveDir() + "\n" + + "ID-MD5=" + EncryptUtil.MD5(selectedArticle.getId()) + "\n" + + "ReadState=" + selectedArticle.getReadStatus() + "\n" + + "ReadUpdated=" + selectedArticle.getReadUpdated() + "\n" + + "StarState=" + selectedArticle.getStarStatus() + "\n" + + "StarUpdated=" + selectedArticle.getStarUpdated() + "\n" + + "SaveStatus=" + selectedArticle.getSaveStatus() + "\n" + + "SaveStatus=" + selectedArticle.getSaveStatus() + "\n" + + "Pubdate=" + selectedArticle.getPubDate() + "\n" + + "Crawldate=" + selectedArticle.getCrawlDate() + "\n" + "Author=" + selectedArticle.getAuthor() + "\n" + - "Published=" + selectedArticle.getPublished() + "\n" + - "Starred=" + selectedArticle.getStarred() + "\n" + - "Categories=" + selectedArticle.getCategories() + "\n" + - "CoverSrc=" + selectedArticle.getCoverSrc() + "\n" + - "OriginHtmlUrl=" + selectedArticle.getOriginHtmlUrl() + "\n" + - "OriginStreamId=" + selectedArticle.getOriginStreamId() + "\n" + - "OriginTitle=" + selectedArticle.getOriginTitle() + "\n" + - "Canonical=" + selectedArticle.getCanonical() + "\n" + -// "Summary=" + selectedArticle.getSummary() + "\n" + - "Content=" + selectedArticle.getContent() + "\n"; + "FeedId=" + selectedArticle.getFeedId() + "\n" + + "Image=" + selectedArticle.getImage() + "\n" + + "Enclosure=" + selectedArticle.getEnclosure() + "\n" + + "【Link】" + selectedArticle.getLink() + "\n" + + "【Summary】" + selectedArticle.getSummary() + "\n\n" + + "【Content】" + document.outerHtml() + "\n"; new MaterialDialog.Builder(this) - .title(R.string.article_about_dialog_title) + .title(R.string.article_info) .content(info) -// .positiveText(R.string.agree) -// .onPositive(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -// //获取剪贴板管理器: -// ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); -// // 创建普通字符型ClipData -// ClipData mClipData = ClipData.newPlainText("FeedId", selectedArticle.getOriginStreamId()); -// // 将ClipData内容放到系统剪贴板里。 -// cm.setPrimaryClip(mClipData); -// ToastUtil.show("已复制订阅源的 RSS 地址"); -// } -// }) + .positiveText(R.string.agree) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + //获取剪贴板管理器: + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + // 创建普通字符型ClipData + ClipData mClipData = ClipData.newPlainText("ArticleContent", ArticleUtil.getPageForDisplay(selectedArticle)); + // 将ClipData内容放到系统剪贴板里。 + cm.setPrimaryClip(mClipData); + ToastUtils.show("已复制文章内容"); + } + }) + .positiveColorRes(R.color.material_red_400) .titleGravity(GravityEnum.CENTER) .titleColorRes(R.color.material_red_400) .contentColorRes(android.R.color.white) .backgroundColorRes(R.color.material_blue_grey_800) .dividerColorRes(R.color.material_teal_a400) - .btnSelector(R.drawable.md_btn_selector_custom, DialogAction.POSITIVE) +// .btnSelector(R.drawable.md_btn_selector_custom, DialogAction.POSITIVE) .positiveColor(Color.WHITE) .negativeColorAttr(android.R.attr.textColorSecondaryInverse) .theme(Theme.DARK) @@ -1251,7 +1480,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { Intent data = new Intent(); data.putExtra("articleNo", articleNo); //注意下面的RESULT_OK常量要与回传接收的Activity中onActivityResult()方法一致 - this.setResult(Api.ActivityResult_ArtToMain, data); + this.setResult(App.ActivityResult_ArtToMain, data); this.finish(); overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); return true; @@ -1263,56 +1492,146 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { @Override protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { mColorfulBuilder - .backgroundColor(R.id.art_root, R.attr.root_view_bg) + .backgroundColor(R.id.article_root, R.attr.root_view_bg) // 设置 toolbar .backgroundColor(R.id.art_toolbar, R.attr.topbar_bg) - .textColor(R.id.art_toolbar_num, R.attr.topbar_fg) + //.textColor(R.id.art_toolbar_num, R.attr.topbar_fg) // 设置中屏和底栏之间的分割线 .backgroundColor(R.id.article_bottombar_divider, R.attr.bottombar_divider) // 设置 bottombar - .backgroundColor(R.id.article_bottombar, R.attr.bottombar_bg) + .backgroundColor(R.id.art_bottombar, R.attr.bottombar_bg) .textColor(R.id.article_bottombar_read, R.attr.bottombar_fg) .textColor(R.id.article_bottombar_star, R.attr.bottombar_fg) - .textColor(R.id.article_feed_config, R.attr.bottombar_fg) -// .textColor(R.id.article_bottombar_tag, R.attr.bottombar_fg) + .textColor(R.id.article_feed_config, R.attr.topbar_fg) + .textColor(R.id.article_bottombar_open_link, R.attr.bottombar_fg) .textColor(R.id.article_bottombar_save, R.attr.bottombar_fg); return mColorfulBuilder; } - private OkHttpClient articleHttpClient = new OkHttpClient.Builder() - .readTimeout(60000L, TimeUnit.MILLISECONDS) - .writeTimeout(60000L, TimeUnit.MILLISECONDS) - .connectTimeout(30000L, TimeUnit.MILLISECONDS) - .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) - .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier).build(); + private OkHttpClient imgHttpClient; + @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_article, menu); + feedMenuItem = menu.findItem(R.id.article_menu_feed); + final Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), selectedArticle.getFeedId()); + if (feed != null) { + feedMenuItem.setVisible(true); + }else { + feedMenuItem.setVisible(false); + } + if(!BuildConfig.DEBUG){ + MenuItem speak = menu.findItem(R.id.article_menu_speak); + speak.setVisible(false); + MenuItem articleInfo = menu.findItem(R.id.article_menu_article_info); + articleInfo.setVisible(false); + MenuItem editContent = menu.findItem(R.id.article_menu_edit_content); + editContent.setVisible(false); + } + return true; + } + + MenuItem feedMenuItem; @Override public boolean onOptionsItemSelected(MenuItem item) { - //监听左上角的返回箭头 - if (item.getItemId() == android.R.id.home) { - finish(); - overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); - return true; + switch (item.getItemId()) { + //监听左上角的返回箭头 + case android.R.id.home: + finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + break; + case R.id.article_menu_feed: + final Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), selectedArticle.getFeedId()); + //final View feedConfigView = findViewById(R.id.article_feed_config); + if (feed != null) { + Intent intent = new Intent(ArticleActivity.this, FeedActivity.class); + intent.putExtra("feedId", selectedArticle.getFeedId()); + startActivity(intent); + } else { + ToastUtils.show("该订阅源已退订,无法编辑"); + } + break; + case R.id.article_menu_speak: + Intent intent = new Intent(ArticleActivity.this, TTSActivity.class); + intent.putExtra("articleNo", articleNo); + startActivity(intent); + break; + case R.id.article_menu_article_info: + showArticleInfo(); + break; + case R.id.article_menu_edit_content: + new MaterialDialog.Builder(ArticleActivity.this) + .title("修改文章内容") + .inputType(InputType.TYPE_CLASS_TEXT) + .inputRange(1, 5600000) + .input(getString(R.string.site_remark), selectedArticle.getContent(), new MaterialDialog.InputCallback() { + @Override + public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { + selectedArticle.setContent(input.toString()); + CoreDB.i().articleDao().update(selectedArticle); + } + }) + .positiveText(R.string.save) + .negativeText(android.R.string.cancel) + .show(); + break; } -// switch (item.getItemId()) { -// case R.id.config_feed: -// openFeed(); -// break; -// } return super.onOptionsItemSelected(item); } + // @Override // public boolean onCreateOptionsMenu(Menu menu) { // getMenuInflater().inflate(R.menu.menu_article_activity, menu); // return true; // } +// private void openMode(){ + // 调用系统默认的图片查看应用 +// Intent intentImage = new Intent(Intent.ACTION_VIEW); +// intentImage.addCategory(Intent.CATEGORY_DEFAULT); +// File file = new File(imageFilePath); +// intentImage.setDataAndType(Uri.fromFile(file), "image/*"); +// startActivity(intentImage); + + // 每次都要选择打开方式 +// startActivity(Intent.createChooser(intentImage, "请选择一款")); - @Override - public void onConfigurationChanged(Configuration config) { - super.onConfigurationChanged(config); - } + // 调起系统默认的图片查看应用(带有选择为默认) +// if(BuildConfig.DEBUG){ +// Intent openImageIntent = new Intent(Intent.ACTION_VIEW); +// openImageIntent.addCategory(Intent.CATEGORY_DEFAULT); +// openImageIntent.setDataAndType(Uri.fromFile(new File(imageFilePath)), "image/*"); +// getDefaultActivity(openImageIntent); +// } +// } +// // 获取默认的打开方式 +// public void getDefaultActivity(Intent intent) { +// PackageManager pm = this.getPackageManager(); +// ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); +// // 如果本应用没有询问过是否要选择默认打开方式,并且没有默认的打开方式,打开默认方式选择狂 +// if (!WithPref.i().hadAskImageOpenMode() || info.activityInfo.packageName.equals("android")) { +// WithPref.i().setHadAskImageOpenMode(true); +// intent.setComponent(new ComponentName("android", "com.android.internal.app.ResolverActivity")); +// } +// startActivity(intent); +// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); +// KLog.i("打开方式", "默认打开方式信息 = " + info + ";pkgName = " + info.activityInfo.packageName); +// } + + // 打开选择默认打开方式的弹窗 +// public void startChooseDialog() { +// Intent intent = new Intent(); +// intent.setAction("android.intent.action.VIEW"); +// intent.addCategory(Intent.CATEGORY_DEFAULT); +// intent.setData(Uri.fromFile(new File(imageFilePath))); +// intent.setComponent(new ComponentName("android","com.android.internal.app.ResolverActivity")); +// startActivity(intent); +// } } diff --git a/app/src/main/java/me/wizos/loread/activity/BaseActivity.java b/app/src/main/java/me/wizos/loread/activity/BaseActivity.java index fded86d..9794073 100644 --- a/app/src/main/java/me/wizos/loread/activity/BaseActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/BaseActivity.java @@ -1,34 +1,21 @@ package me.wizos.loread.activity; -import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.view.KeyEvent; -import com.socks.library.KLog; +import androidx.appcompat.app.AppCompatActivity; import me.wizos.loread.App; import me.wizos.loread.R; -import me.wizos.loread.data.WithPref; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; import me.wizos.loread.view.colorful.Colorful; /** * @author Wizos on 2016/3/12. */ public abstract class BaseActivity extends AppCompatActivity { - private static String TAG = ""; - /* - * LOG打印标签 - * getClass()获得当前对象的类型 - * java中有Class类,用以描述类型信息. - * 如用语句 Class theClass="hello".getClass(); - * 得到的就是字符串的类型.getSimpleName()返回源代码中给出的底层类的简称。 - */ - - /* - * http://blog.csdn.net/sinat_31311947/article/details/50619467 - * 在实例子类的时候,如果父类的构造器中使用了this,其实这个this也还是指向子类这个this。 - */ + private static String TAG = "BaseActivity"; @Override protected void onCreate(Bundle savedInstanceState) { @@ -36,53 +23,60 @@ protected void onCreate(Bundle savedInstanceState) { showCurrentTheme(); } - - protected void goTo(String toActivity) { - Intent intent; - if (TAG.equals(toActivity)) { - KLog.i(this.toString() + "【跳转无效,为当前页】"); - return; - }else if(toActivity.equals(MainActivity.TAG)){ - intent = new Intent(this, MainActivity.class); - }else if(toActivity.equals(LoginActivity.TAG)){ - intent = new Intent(this, LoginActivity.class); -// }else if(toActivity.equals(TagActivity.TAG)){ -// intent = new Intent(this, TagActivity.class); - } else if (toActivity.equals(ArticleActivity.TAG)) { - intent = new Intent(this, ArticleActivity.class); - }else { - return; - } - startActivity(intent); - } - +// /** +// * // 获取当前的内容供应商 +// * // 0是未登录 +// * // 1是本地rss +// * // 2是inoreader +// * // 3是feedly +// * // 4是tinytinyrss +// * 初始化Api,Contract,DB +// */ +// public void route() { +// Intent intent; +// String uid = App.i().getKeyValue().getString(Contract.UID, null); +// KLog.e("获取UID:" + uid ); +// if ( TextUtils.isEmpty(uid) ) { +// intent = new Intent(this, ProviderActivity.class); +// } else { +// intent = new Intent(this, MainActivity.class); +// } +// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); +// startActivity(intent); +// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); +// } protected Colorful mColorful; protected abstract Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder); + /** * 自动设置当前主题(设置各个视图与颜色属性的关联) */ protected void showCurrentTheme() { Colorful.Builder mColorfulBuilder = new Colorful.Builder(this); mColorful = buildColorful(mColorfulBuilder).create(); - if (WithPref.i().getThemeMode() == App.Theme_Day) { - mColorful.setTheme(R.style.AppTheme_Day); - } else { + if (App.i().getUser() != null && App.i().getUser().getThemeMode() == App.THEME_NIGHT) { mColorful.setTheme(R.style.AppTheme_Night); + } else { + mColorful.setTheme(R.style.AppTheme_Day); } } + /** * 手动切换主题并保存 */ protected void manualToggleTheme() { - if (WithPref.i().getThemeMode() == App.Theme_Day) { + User user = App.i().getUser(); + if (App.i().getUser().getThemeMode() == App.THEME_DAY) { mColorful.setTheme(R.style.AppTheme_Night); - WithPref.i().setThemeMode(App.Theme_Night); + user.setThemeMode(App.THEME_NIGHT); + } else { mColorful.setTheme(R.style.AppTheme_Day); - WithPref.i().setThemeMode(App.Theme_Day); + user.setThemeMode(App.THEME_DAY); } + CoreDB.i().userDao().update(user); } diff --git a/app/src/main/java/me/wizos/loread/activity/FeedActivity.java b/app/src/main/java/me/wizos/loread/activity/FeedActivity.java new file mode 100644 index 0000000..ee9962f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/FeedActivity.java @@ -0,0 +1,565 @@ +package me.wizos.loread.activity; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.InputType; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.Toolbar; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.carlt.networklibs.utils.NetworkUtils; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.hjq.toast.ToastUtils; +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.enums.PopupAnimation; +import com.noober.background.BackgroundLibrary; +import com.noober.background.drawable.DrawableCreator; +import com.socks.library.KLog; + +import java.util.ArrayList; +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.BuildConfig; +import me.wizos.loread.R; +import me.wizos.loread.bean.feedly.CategoryItem; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.config.SaveDirectory; +import me.wizos.loread.config.TestConfig; +import me.wizos.loread.config.Unsubscribe; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.FeedCategory; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.ScreenUtil; +import me.wizos.loread.utils.UriUtil; +import me.wizos.loread.view.IconFontView; +import me.wizos.loread.view.colorful.Colorful; + +public class FeedActivity extends BaseActivity { + Toolbar toolbar; + FloatingActionButton fab; + TextView descriptionView; + TextView descriptionLabelView; + TextView siteLinkLabelView; + TextView rssLinkLabelView; + TextView subscribersView; + TextView updatedView; + TextView siteLinkView; + TextView feedLinkView; + Feed feed; + ArrayList preCategoryItems; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + BackgroundLibrary.inject2(this); + setContentView(R.layout.activity_feed); + toolbar = findViewById(R.id.feed_toolbar); + setSupportActionBar(toolbar); + // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 + getSupportActionBar().setHomeButtonEnabled(true); + // 决定左上角图标的左侧是否有向左的小箭头,true 有小箭头 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); + + fab = findViewById(R.id.fab); + descriptionLabelView = findViewById(R.id.feed_desc_label); + siteLinkLabelView = findViewById(R.id.feed_site_link_label); + rssLinkLabelView = findViewById(R.id.feed_rss_link_label); + descriptionView = findViewById(R.id.feed_description); + subscribersView = findViewById(R.id.feed_subscribers); + updatedView = findViewById(R.id.feed_updated); + siteLinkView = findViewById(R.id.feed_site_link); + feedLinkView = findViewById(R.id.feed_rss_link); + + Bundle bundle; + if (savedInstanceState != null) { + bundle = savedInstanceState; + } else { + bundle = getIntent().getExtras(); + } + + if (bundle == null) { + return; + } + + String feedId = bundle.getString("feedId"); + if (TextUtils.isEmpty(feedId)) { + return; + } + feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(),feedId); + if( null == feed){ + finish(); + return; + } + String feedUrlId = "feed/" + feed.getFeedUrl(); + KLog.i("展示feed的详情:" + feedId + "," + feedUrlId + " , " + feed); + + RequestOptions options = new RequestOptions().circleCrop(); + + Glide.with(this).load(UriUtil.getFaviconUrl(feed.getHtmlUrl())).apply(options).into(fab); + + getSupportActionBar().setTitle(feed.getTitle()); + toolbar.setSubtitle(feed.getFeedUrl()); + siteLinkView.setText(feed.getHtmlUrl()); + feedLinkView.setText(feed.getFeedUrl()); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if(TextUtils.isEmpty(feed.getHtmlUrl())){ + //ToastUtils.show(); + return; + } + Intent intent = new Intent(FeedActivity.this, WebActivity.class); + intent.setData(Uri.parse(feed.getHtmlUrl())); + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + }); + +// Retrofit retrofit = new Retrofit.Builder() +// .baseUrl(FeedlyApi.HOST + "/") // 设置网络请求的Url地址, 必须以/结尾 +// .client(HttpClientManager.i().simpleClient()) +// .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 +// .build(); +// +// FeedlyService feedlyService = retrofit.create(FeedlyService.class); +// +// //对 发送请求 进行封装 +// Call callFeedMeta = feedlyService.getFeedMeta(feedUrlId); +// +// callFeedMeta.enqueue(new Callback() { +// //请求成功时回调 +// @Override +// public void onResponse(Call call, Response response) { +// if (!response.isSuccessful()) { +// return; +// } +// +// FeedItem feedItem = response.body(); +// // 对返回数据进行处理 +// //KLog.e("取到数据:" + response.body().toString()); +// if (!TextUtils.isEmpty(feedItem.getDescription())) { +//// descriptionLabelView.setVisibility(View.VISIBLE); +// descriptionView.setVisibility(View.VISIBLE); +// descriptionView.setText(feedItem.getDescription()); +// } +// +// subscribersView.setVisibility(View.VISIBLE); +// updatedView.setVisibility(View.VISIBLE); +// +// subscribersView.setText(feedItem.getSubscribers() + " 关注"); +// updatedView.setText(TimeUtil.stampToTime(feedItem.getUpdated(), "yyyy-MM-dd") + " 更新"); +// } +// +// //请求失败时候的回调 +// @Override +// public void onFailure(Call call, Throwable throwable) { +// KLog.e("连接失败" + throwable); +// } +// }); + + createItemView2(feed); + } + + private View categoryView; + private View remarkView; + + private void createItemView2(final Feed feed) { + LayoutInflater inflater = getLayoutInflater(); + LinearLayout linearLayout = findViewById(R.id.feed_summary); + + // 增加设置项 + View settingSession = inflater.inflate(R.layout.setting_item_session, linearLayout, false); + ((TextView) settingSession.findViewById(R.id.setting_session_title)).setText(R.string.settings); + linearLayout.addView(settingSession); + + remarkView = inflater.inflate(R.layout.setting_item_arrow, linearLayout, false); + ((TextView) remarkView.findViewById(R.id.setting_item_title)).setText(R.string.remark); + if (!TextUtils.isEmpty(feed.getTitle())) { + ((TextView) remarkView.findViewById(R.id.setting_item_value)).setText(feed.getTitle()); + } else { + ((TextView) remarkView.findViewById(R.id.setting_item_value)).setText(R.string.unknown); + } + remarkView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new MaterialDialog.Builder(FeedActivity.this) + .title(R.string.site_remark) + .inputType(InputType.TYPE_CLASS_TEXT) + .inputRange(1, 56) + .input(getString(R.string.site_remark), feed.getTitle(), new MaterialDialog.InputCallback() { + @Override + public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { + if (!NetworkUtils.isAvailable()) { + ToastUtils.show(getString(R.string.tips_no_net)); + } else { + renameFeed(input.toString(), feed); + dialog.dismiss(); + } + } + }) + .positiveText(R.string.confirm) + .negativeText(android.R.string.cancel) + .show(); + } + }); + linearLayout.addView(remarkView); + + final EditFeed editFeed = new EditFeed(feed.getId()); + preCategoryItems = editFeed.getCategoryItems(); + final String[] preCategoryTitles = new String[preCategoryItems.size()]; + for (int i = 0, size = preCategoryItems.size(); i < size; i++) { + preCategoryTitles[i] = preCategoryItems.get(i).getLabel(); + } + String titles = TextUtils.join(" / ", preCategoryTitles); + + + categoryView = inflater.inflate(R.layout.setting_item_arrow, linearLayout, false); + ((TextView) categoryView.findViewById(R.id.setting_item_title)).setText(getString(R.string.category)); + if (!TextUtils.isEmpty(titles)) { + ((TextView) categoryView.findViewById(R.id.setting_item_value)).setText(titles); + } else { + ((TextView) categoryView.findViewById(R.id.setting_item_value)).setText(getString(R.string.no_thing)); + } + categoryView.setOnClickListener(v -> { + final List categoryList = CoreDB.i().categoryDao().getAll(App.i().getUser().getId()); + ArrayMap categoryMap = new ArrayMap<>(categoryList.size()); + + String[] categoryTitleArray = new String[categoryList.size()]; + for (int i = 0, size = categoryList.size(); i < size; i++) { + categoryMap.put(categoryList.get(i).getId(), i); + categoryTitleArray[i] = categoryList.get(i).getTitle(); + } + + final Integer[] beforeSelectedIndices = new Integer[]{preCategoryItems.size()}; + for (int i = 0, size = preCategoryItems.size(); i < size; i++) { + beforeSelectedIndices[i] = categoryMap.get(preCategoryItems.get(i).getId()); + } + + new MaterialDialog.Builder(FeedActivity.this) + .title(getString(R.string.edit_category)) + .items(categoryTitleArray) + .itemsCallbackMultiChoice(beforeSelectedIndices, new MaterialDialog.ListCallbackMultiChoice() { + @Override + public boolean onSelection(MaterialDialog dialog, final Integer[] which, CharSequence[] text) { + final ArrayList selectedCategoryItems = new ArrayList<>(); + ArrayList selectedTitles = new ArrayList<>(); + CategoryItem categoryItem; + for (int i : which) { + categoryItem = new CategoryItem(); + categoryItem.setId(categoryList.get(i).getId()); + categoryItem.setLabel(categoryList.get(i).getTitle()); + selectedCategoryItems.add(categoryItem); + selectedTitles.add(categoryList.get(i).getTitle()); + } + final String titles1 = TextUtils.join(" / ", selectedTitles); + editFeed.setCategoryItems(selectedCategoryItems); + ToastUtils.show(R.string.editing); + App.i().getApi().editFeedCategories(preCategoryItems, editFeed, new CallbackX() { + @Override + public void onSuccess(String result) { + ArrayList feedCategories = new ArrayList<>(selectedCategoryItems.size()); + FeedCategory feedCategory; + for (CategoryItem categoryItem : selectedCategoryItems) { + feedCategory = new FeedCategory(App.i().getUser().getId(), feed.getId(), categoryItem.getId()); + feedCategories.add(feedCategory); + } + ((TextView) categoryView.findViewById(R.id.setting_item_value)).setText(titles1); + CoreDB.i().coverFeedCategories(editFeed); + preCategoryItems = selectedCategoryItems; + ToastUtils.show(R.string.edit_success); + } + + @Override + public void onFailure(String error) { + ToastUtils.show(R.string.edit_fail); + } + }); + return true; + } + }) + .alwaysCallMultiChoiceCallback() // the callback will always be called, to check if selection is still allowed + .show(); + + }); + linearLayout.addView(categoryView); + + final View displayView = inflater.inflate(R.layout.setting_item_arrow, linearLayout, false); + ((TextView) displayView.findViewById(R.id.setting_item_title)).setText(R.string.select_display_mode); + final TextView displayValueView = displayView.findViewById(R.id.setting_item_value); + displayValueView.setText(TestConfig.i().getDisplayMode(feed.getId())); + displayView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new XPopup.Builder(FeedActivity.this) + .isCenterHorizontal(true) //是否与目标水平居中对齐 + .offsetY(-10) + .hasShadowBg(true) + .popupAnimation(PopupAnimation.ScaleAlphaFromCenter) + .atView(displayValueView) // 依附于所点击的View,内部会自动判断在上方或者下方显示 + .asAttachList(new String[]{getString(R.string.rss), getString(R.string.readability), getString(R.string.original)}, + null, + (which, text) -> { + String selectedFeedDisplayMode = App.DISPLAY_RSS; + + switch (which) { + case 0: + selectedFeedDisplayMode = App.DISPLAY_RSS; + break; + case 1: + selectedFeedDisplayMode = App.DISPLAY_READABILITY; + break; + case 2: + selectedFeedDisplayMode = App.DISPLAY_LINK; + break; + default: + break; + } + + if (!TextUtils.isEmpty(TestConfig.i().getDisplayMode(feed.getId()))) { + if (selectedFeedDisplayMode.equals(TestConfig.i().getDisplayMode(feed.getId()))) { + return; + } + TestConfig.i().removeDisplayRouter(feed.getId()); + TestConfig.i().addDisplayRouter(feed.getId(), selectedFeedDisplayMode); + TestConfig.i().save(); + displayValueView.setText(selectedFeedDisplayMode); + } + }) + .show(); + } + }); + linearLayout.addView(displayView); + + + + + if(!BuildConfig.DEBUG){ + return; + } + View saveFolderView = inflater.inflate(R.layout.setting_item_arrow, linearLayout, false); + ((TextView) saveFolderView.findViewById(R.id.setting_item_title)).setText(R.string.save_directory); + + final String optionName = SaveDirectory.i().getDirNameSettingByFeed(feed.getId()); + final TextView saveFolderValueView = saveFolderView.findViewById(R.id.setting_item_value); + saveFolderValueView.setText(optionName); + + saveFolderView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + new XPopup.Builder(FeedActivity.this) + .isCenterHorizontal(true) //是否与目标水平居中对齐 + .offsetY(-10) + .hasShadowBg(true) + .popupAnimation(PopupAnimation.ScaleAlphaFromCenter) + .atView(saveFolderValueView) // 依附于所点击的View,内部会自动判断在上方或者下方显示 + .asAttachList(SaveDirectory.i().getDirectoriesOptionName(), + null, + (which, text) -> { + List dirsValue = SaveDirectory.i().getDirectoriesOptionValue(); + SaveDirectory.i().setFeedDirectory(feed.getId(),dirsValue.get(which)); + SaveDirectory.i().save(); + }) + .show(); + } + }); + linearLayout.addView(saveFolderView); + } + + public void renameFeed(final String renamedTitle, final Feed feed) { + KLog.e("=====" + renamedTitle + feed.getId()); + if (renamedTitle.equals("") || feed.getTitle().equals(renamedTitle)) { + return; + } + App.i().getApi().renameFeed(feed.getId(), renamedTitle, new CallbackX() { + @Override + public void onSuccess(Object result) { + feed.setTitle(renamedTitle); + CoreDB.i().feedDao().update(feed); + ToastUtils.show(R.string.edit_success); + KLog.e("改了名字:" + renamedTitle); + } + + @Override + public void onFailure(Object error) { + ToastUtils.show(App.i().getString(R.string.rename_failed)); + } + }); + } + + public void copyIconUrl(@Nullable View view) { + if (feed == null || TextUtils.isEmpty(feed.getIconUrl())) { + return; + } + //获取剪贴板管理器: + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + // 创建普通字符型ClipData + ClipData mClipData = ClipData.newRawUri(feed.getTitle(), Uri.parse(feed.getIconUrl())); + // 将ClipData内容放到系统剪贴板里。 + cm.setPrimaryClip(mClipData); + ToastUtils.show(R.string.copy_success); + } + + public void copyHtmlUrl(@Nullable View view) { + if (feed == null || TextUtils.isEmpty(feed.getHtmlUrl())) { + return; + } + //获取剪贴板管理器: + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + // 创建普通字符型ClipData + ClipData mClipData = ClipData.newRawUri(feed.getTitle(), Uri.parse(feed.getHtmlUrl())); + // 将ClipData内容放到系统剪贴板里。 + cm.setPrimaryClip(mClipData); + ToastUtils.show(R.string.copy_success); + } + + public void copyFeedUrl(@Nullable View view) { + if (feed==null || TextUtils.isEmpty(feed.getFeedUrl())) { + return; + } + //获取剪贴板管理器: + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + // 创建普通字符型ClipData + ClipData mClipData = ClipData.newRawUri(feed.getTitle(), Uri.parse(feed.getFeedUrl())); + // 将ClipData内容放到系统剪贴板里。 + cm.setPrimaryClip(mClipData); + ToastUtils.show(R.string.copy_success); + } + + private Integer[] selectIndices; + + public void showSelectFolder(final View view, final String feedId) { + final List categoryList = CoreDB.i().categoryDao().getAll(App.i().getUser().getId()); + String[] categoryTitleArray = new String[categoryList.size()]; + for (int i = 0, size = categoryList.size(); i < size; i++) { + categoryTitleArray[i] = categoryList.get(i).getTitle(); + } + final EditFeed editFeed = new EditFeed(); + editFeed.setId(feedId); + new MaterialDialog.Builder(this) + .title(getString(R.string.select_category)) + .items(categoryTitleArray) + .alwaysCallMultiChoiceCallback() + .itemsCallbackMultiChoice(null, (dialog, which, text) -> { + FeedActivity.this.selectIndices = which; + for (int i : which) { + KLog.e("点选了:" + i); + } + return true; + }) + .positiveText(R.string.confirm) + .onPositive((dialog, which) -> { + ArrayList categoryItemList = new ArrayList<>(); + for (Integer selectIndex : selectIndices) { + CategoryItem categoryItem = new CategoryItem(); + categoryItem.setId(categoryList.get(selectIndex).getId()); + categoryItemList.add(categoryItem); + } + editFeed.setCategoryItems(categoryItemList); + view.setClickable(false); + App.i().getApi().addFeed(editFeed, new CallbackX() { + @Override + public void onSuccess(Object result) { + KLog.e("添加成功"); + ((IconFontView) view).setText(R.string.font_tick); + ToastUtils.show(R.string.subscribe_success); + view.setClickable(true); + } + + @Override + public void onFailure(Object error) { + ToastUtils.show(getString(R.string.subscribe_fail)); + view.setClickable(true); + } + }); + }).show(); + } + + public void clickUnsubscribe(final View view) { + if (feed == null) { + return; + } + if (CoreDB.i().feedDao().getById(App.i().getUser().getId(), feed.getId()) == null) { + showSelectFolder(view, feed.getId()); + } else { + new MaterialDialog.Builder(this) + .title(R.string.warning) + .content(R.string.are_you_sure_that_unsubscribe_this_feed_link) + .positiveText(R.string.confirm) + .negativeText(R.string.cancel) + .positiveColor(Color.RED) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + App.i().getApi().unsubscribeFeed(feed.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + KLog.e("退订成功"); + ToastUtils.show(getString(R.string.unsubscribe_succeeded)); + ((AppCompatButton) view).setText(R.string.subscribe); + Drawable drawable = new DrawableCreator.Builder() + .setRipple(true, getResources().getColor(R.color.primary)) + .setPressedSolidColor(getResources().getColor(R.color.primary), getResources().getColor(R.color.bluePrimary)) + .setSolidColor(getResources().getColor(R.color.bluePrimary)) + .setCornersRadius(ScreenUtil.dp2px(30)) + .build(); + view.setBackground(drawable); + + List feeds = new ArrayList<>(); + feeds.add(feed); + Unsubscribe.genBackupFile2(App.i().getUser(), feeds); + CoreDB.i().feedCategoryDao().deleteByFeedId(feed.getUid(), feed.getId()); + CoreDB.i().articleDao().deleteUnStarByFeedId(feed.getUid(), feed.getId()); + CoreDB.i().feedDao().delete(feed); + } + + @Override + public void onFailure(Object error) { + KLog.e("失败:" + error); + ToastUtils.show(getString(R.string.unsubscribe_failed,error)); + } + }); + + } + }).build().show(); + } + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + //监听左上角的返回箭头 + if (item.getItemId() == android.R.id.home) { + finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + } + return super.onOptionsItemSelected(item); + } + @Override + protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + return mColorfulBuilder; + } +} diff --git a/app/src/main/java/me/wizos/loread/activity/ImageActivity.java b/app/src/main/java/me/wizos/loread/activity/ImageActivity.java deleted file mode 100644 index b366cd6..0000000 --- a/app/src/main/java/me/wizos/loread/activity/ImageActivity.java +++ /dev/null @@ -1,123 +0,0 @@ -package me.wizos.loread.activity; - -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.bumptech.glide.Glide; - -import java.io.File; - -import me.wizos.loread.R; -import me.wizos.loread.adapter.MaterialSimpleListAdapter; -import me.wizos.loread.adapter.MaterialSimpleListItem; -import me.wizos.loread.utils.FileUtil; -import me.wizos.loread.utils.ToastUtil; -import me.wizos.loread.view.DragPhotoView; - -/** - * An example full-screen activity that shows and hides the system UI (i.e. - * status bar and navigation/system bar) with user interaction. - */ - -/** - * @author Wizos on 2018/06/06 - */ -public class ImageActivity extends AppCompatActivity { - private Uri imageUri; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_image); - Intent imageIntent = getIntent(); - imageUri = imageIntent.getData(); - -// KLog.e("图片路径" + imageUri.getPath() + " " + imageUri.getHost() ); - DragPhotoView originalImage = findViewById(R.id.image_photo_view); - - - //必须添加一个onExitListener,在拖拽到底部时触发 - originalImage.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - originalImage.setOnExitListener(new DragPhotoView.OnExitListener() { - @Override - public void onExit(DragPhotoView dragPhotoView, float v, float v1, float v2, float v3) { - exit(); - } - }); - - originalImage.setOnTapListener(new DragPhotoView.OnTapListener() { - @Override - public void onTap(DragPhotoView dragPhotoView) { - exit(); - } - - @Override - public void onLongTap(me.wizos.loread.view.DragPhotoView view) { - MaterialSimpleListAdapter adapter = new MaterialSimpleListAdapter(ImageActivity.this); - adapter.add(new MaterialSimpleListItem.Builder(ImageActivity.this) - .content(R.string.image_dialog_save_img) - .backgroundColor(Color.TRANSPARENT) - .build()); - adapter.add(new MaterialSimpleListItem.Builder(ImageActivity.this) - .content(R.string.image_dialog_share_img) - .backgroundColor(Color.TRANSPARENT) - .build()); - new MaterialDialog.Builder(ImageActivity.this) - .adapter(adapter, new MaterialDialog.ListCallback() { - @Override - public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { - dialog.dismiss(); - switch (which) { - case 0: -// KLog.e("正在复制图片", imagePath + " " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/"+ new File(imagePath).getName() ); - try { - FileUtil.copyFileToPictures(new File(imageUri.getPath())); -// FileUtil.copyFileToPictures(new File("/storage/emulated/0/Android/data/me.wizos.loread/files/cache/1d3403c4bd6519744ffc7efd2dbf2e60/1d3403c4bd6519744ffc7efd2dbf2e60_files/0-0f6ebf6b5ba6e4c6eac8946be9ca8153874e469b")); - ToastUtil.showLong("图片已经保存到相册"); - } catch (Exception e) { - return; - } - break; - case 1: -// Uri imageUri = Uri.fromFile( new File(imagePath ) ); - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri); - shareIntent.setType("image/*"); - startActivity(Intent.createChooser(shareIntent, "分享图片")); - break; - default: - break; - } - } - }) - .show(); - } - }); - originalImage.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - exit(); - } - }); - - Glide.with(this) - .load(imageUri) - .into(originalImage); - } - - private void exit() { - ImageActivity.this.finish(); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - } -} diff --git a/app/src/main/java/me/wizos/loread/activity/LabActivity.java b/app/src/main/java/me/wizos/loread/activity/LabActivity.java new file mode 100644 index 0000000..2f9f1b7 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/LabActivity.java @@ -0,0 +1,299 @@ +package me.wizos.loread.activity; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.ArrayMap; +import android.view.View; +import android.widget.EditText; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.work.Constraints; +import androidx.work.NetworkType; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.hjq.toast.ToastUtils; +import com.socks.library.KLog; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.config.AdBlock; +import me.wizos.loread.config.ArticleActionConfig; +import me.wizos.loread.config.LinkRewriteConfig; +import me.wizos.loread.config.NetworkRefererConfig; +import me.wizos.loread.config.NetworkUserAgentConfig; +import me.wizos.loread.config.TestConfig; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.ArticleTag; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Tag; +import me.wizos.loread.db.User; +import me.wizos.loread.network.SyncWorker; +import me.wizos.loread.utils.EncryptUtil; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; + +import static androidx.work.ExistingPeriodicWorkPolicy.KEEP; +import static me.wizos.loread.Contract.SCHEMA_HTTP; +import static me.wizos.loread.Contract.SCHEMA_HTTPS; + + +public class LabActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_lab); + } + + + private MaterialDialog materialDialog; + public void onClickBackup(View view) { + materialDialog = new MaterialDialog.Builder(this) + .title("正在处理") + .content("请耐心等待下") + .progress(true, 0) + .canceledOnTouchOutside(false) + .progressIndeterminateStyle(false) + .show(); + new Thread(new Runnable() { + @Override + public void run() { + FileUtil.backup(); + materialDialog.dismiss(); + } + }).start(); + + } + + public void onClickRestore(View view) { + materialDialog = new MaterialDialog.Builder(this) + .title("正在处理") + .content("请耐心等待下") + .progress(true, 0) + .canceledOnTouchOutside(false) + .progressIndeterminateStyle(false) + .show(); + new Thread(new Runnable() { + @Override + public void run() { + FileUtil.restore(); + materialDialog.dismiss(); + } + }).start(); + } + + + public void onClickReadConfig(View view) { + materialDialog = new MaterialDialog.Builder(this) + .content("正在读取") + .progress(true, 0) + .canceledOnTouchOutside(false) + .progressIndeterminateStyle(false) + .show(); + TestConfig.i().reset(); + AdBlock.i().reset(); + LinkRewriteConfig.i().reset(); + NetworkRefererConfig.i().reset(); + NetworkUserAgentConfig.i().reset(); + // UserConfig.i().reset(); + materialDialog.dismiss(); + } + + + public void onClickArrangeCrawlDateArticle(View view) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + List

articles = CoreDB.i().articleDao().getAllNoOrder(App.i().getUser().getId()); + for (Article article:articles) { + article.setCrawlDate(article.getPubDate()); + } + CoreDB.i().articleDao().update(articles); + KLog.i("整理完成:" + articles.size()); + } + }); + } + + public void onClickClearHtmlDir(View view) { + AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + clearHtmlDir(); + } + }); + } + + /** + * 将某些没有被清理到的缓存文件夹给清理掉 + */ + private void clearHtmlDir() { + //List
articles = WithDB.i().getArtsAllNoOrder(); + List
articles = CoreDB.i().articleDao().getAllNoOrder(App.i().getUser().getId()); + ArrayMap temp = new ArrayMap<>(articles.size()); + + for (Article article : articles) { + temp.put(EncryptUtil.MD5(article.getId()), "1"); + } + + + File dir = new File(App.i().getUserCachePath()); + File[] arts = dir.listFiles(); + KLog.e("文件数量:" + arts.length); + String x = ""; + for (File sourceFile : arts) { + x = temp.get(sourceFile.getName()); + if (null == x) { + KLog.e("移动文件名:" + " " + sourceFile.getName()); + FileUtil.moveDir(sourceFile.getAbsolutePath(), App.i().getUserFilesDir() + "/move/" + sourceFile.getName()); + } + } + } + + + public void startSyncWorkManager(View view) { + ToastUtils.show("开始 同步WorkManager"); + // Constraints 指明工作何时可以运行 + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + PeriodicWorkRequest syncRequest = new PeriodicWorkRequest.Builder(SyncWorker.class, 15, TimeUnit.MINUTES) + .setConstraints(constraints) + .build(); + WorkManager.getInstance(this).enqueueUniquePeriodicWork(SyncWorker.TAG,KEEP,syncRequest); + } + + public void stopWorkManager(View view) { + ToastUtils.show("取消 WorkManager"); + WorkManager.getInstance(this).cancelAllWork(); + } + + + public void openActivity(View view){ + EditText editText = findViewById(R.id.lab_enter_edittext); + String url = editText.getText().toString(); + KLog.e( "获取的url:" + url ); + int enterSize = getMatchActivitiesSize(url); + int wizosSize = getMatchActivitiesSize("https://wizos.me"); + Intent intent; + if( !App.i().getUser().isOpenLinkBySysBrowser() && (url.startsWith(SCHEMA_HTTP) || url.startsWith(SCHEMA_HTTPS)) && enterSize == wizosSize){ + intent = new Intent(LabActivity.this, WebActivity.class); + intent.setData(Uri.parse(url)); + intent.putExtra("theme", App.i().getUser().getThemeMode()); + }else { + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + } + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + + private int getMatchActivitiesSize(String url){ + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + PackageManager packageManager = getPackageManager(); + List list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo:list) { + KLog.e( "适配的包名:" + resolveInfo.activityInfo.packageName ); + } + return list.size(); + } + +// public void loginAccount(){ +// // TODO: 2020/4/14 开始模拟登录 +// if(!Config.i().enableAuth){ +// return; +// } +// handleAccount(); +// +// Account account = new Account(getString(R.string.app_name),ACCOUNT_TYPE); +// // 帐户密码和信息这里用null演示 +// mAccountManager.addAccountExplicitly(account, null, null); +// // 自动同步 +// Bundle bundle= new Bundle(); +// ContentResolver.setIsSyncable(account, AccountProvider.AUTHORITY, 1); +// ContentResolver.setSyncAutomatically(account, AccountProvider.AUTHORITY,true); +// ContentResolver.addPeriodicSync(account, AccountProvider.AUTHORITY,bundle, 30); // 间隔时间为30秒 +// // 手动同步 +//// ContentResolver.requestSync(account, AccountProvider.AUTHORITY, bundle); +//// finish(); +// } + + public void onClickClearTags(View view) { + CoreDB.i().tagDao().clear(App.i().getUser().getId()); + CoreDB.i().articleTagDao().clear(App.i().getUser().getId()); + } + + public void onClickGenTags(View view) { + String uid = App.i().getUser().getId(); + List
articles = CoreDB.i().articleDao().getNotTagStar(uid,0); + List articleTags = new ArrayList<>(); + KLog.e("设置 没有tag的 数据:" + articles.size() ); + Set tagTitleSet = new HashSet<>(); + for (Article article: articles){ + KLog.e("article feedId:" + article.getFeedId() ); + if(StringUtils.isEmpty(article.getFeedId())){ + continue; + } + KLog.e("article 数据:" + article); + List categories = CoreDB.i().categoryDao().getByFeedId(uid,article.getFeedId()); + for (Category category:categories) { + tagTitleSet.add(category.getTitle()); + ArticleTag articleTag = new ArticleTag(uid, article.getId(), category.getId()); + articleTags.add(articleTag ); + KLog.e("设置 articleTag 数据:" + articleTag); + } + } + + List tags = new ArrayList<>(tagTitleSet.size()); + for (String title:tagTitleSet) { + Tag tag = new Tag(); + tag.setUid(uid); + tag.setId(title); + tag.setTitle(title); + tags.add(tag); + KLog.e("设置 Tag 数据:" + tag); + } + CoreDB.i().tagDao().insert(tags); + CoreDB.i().articleTagDao().insert(articleTags); + } + + public void onClickEditHost(View view) { + User user = App.i().getUser(); + if(user==null){ + ToastUtils.show("当前用户不存在"); + return; + } + + EditText editText = findViewById(R.id.lab_enter_edittext); + String url = editText.getText().toString(); + user.setHost(url); + CoreDB.i().userDao().insert(user); + } + + public void actionArticle(View view){ + User user = App.i().getUser(); + if(user==null){ + ToastUtils.show("当前用户不存在"); + return; + } + ArticleActionConfig.i().exeRules(App.i().getUser().getId(), 0); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/activity/LoginActivity.java b/app/src/main/java/me/wizos/loread/activity/LoginActivity.java deleted file mode 100644 index 0c578b1..0000000 --- a/app/src/main/java/me/wizos/loread/activity/LoginActivity.java +++ /dev/null @@ -1,217 +0,0 @@ -package me.wizos.loread.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; - -import com.kyleduo.switchbutton.SwitchButton; -import com.socks.library.KLog; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.Locale; - -import me.wizos.loread.R; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.event.Login; -import me.wizos.loread.net.Api; -import me.wizos.loread.service.MainService; -import me.wizos.loread.utils.ToastUtil; -import me.wizos.loread.view.colorful.Colorful; - -/** - * @author Wizos on 2016/3/5. - */ -public class LoginActivity extends BaseActivity implements View.OnLayoutChangeListener{ - protected static final String TAG = "LoginActivity"; - protected String mAccountID = ""; - protected String mAccountPD = ""; - private EditText idEditText, pdEditText; - private Button loginButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); -// KLog.e("【未登录】"); - forInput(); - initView(); - recoverData(); -// initBroadcast(); - //注册 - EventBus.getDefault().register(this); - } - - - @Override - protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { - return mColorfulBuilder; - } - - - @Override - public void onDestroy() { -// manager.unregisterReceiver(localReceiver); - EventBus.getDefault().unregister(this); - super.onDestroy(); - } - - - /** - * 事件响应方法 - * 接收消息 - * - * @param loginEvent - */ - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(Login loginEvent) { - if (loginEvent.isSuccess()) { - Intent intentToActivity = new Intent(LoginActivity.this, MainActivity.class); - intentToActivity.setAction("firstSetupStart"); - startActivity(intentToActivity); - LoginActivity.this.finish(); - } else { - loginButton.setEnabled(true); - idEditText.setEnabled(true); - pdEditText.setEnabled(true); - ToastUtil.showLong(getString(R.string.tips_login_failure)); - } - } - - private void initView() { - idEditText = findViewById(R.id.edittext_id); - pdEditText = findViewById(R.id.edittext_pd); - loginButton = findViewById(R.id.login_button_login); - SwitchButton inoreaderProxy = findViewById(R.id.setting_proxy_sb); - inoreaderProxy.setChecked(WithPref.i().isInoreaderProxy()); - } - - - /** - * 默认填充密码 - */ - private void recoverData() { - mAccountID = WithPref.i().getAccountID(); - mAccountPD = WithPref.i().getAccountPD(); - if (!TextUtils.isEmpty(mAccountID)) { - idEditText.setText(mAccountID); - } - if (!TextUtils.isEmpty(mAccountPD)) { - pdEditText.setText(mAccountPD); - } - } - - private void forInput() { - activityRootView = findViewById(R.id.login_scroll); - screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();//获取屏幕高度 - keyHeight = screenHeight / 3; //阀值设置为屏幕高度的1/3 - } - - //Activity最外层的Layout视图 - private View activityRootView; - //屏幕高度 - private int screenHeight = 0; - //软件盘弹起后所占高度阀值 - private int keyHeight = 0; - @Override - protected void onResume() { - super.onResume(); - //添加layout大小发生改变监听器 - activityRootView.addOnLayoutChangeListener(this); - } - - @Override - public void onLayoutChange(View v, int left, int top, int right,int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { -// Space space = findViewById(R.id.login_space_a); -// if(oldBottom != 0 && bottom != 0 &&(oldBottom - bottom > keyHeight)){ -//// if(space.getVisibility() == View.VISIBLE){ -//// space.setVisibility(View.GONE); -//// } -// }else if(oldBottom != 0 && bottom != 0 &&(bottom - oldBottom > keyHeight)){ -//// if(space.getVisibility() == View.GONE){ -//// space.setVisibility(View.VISIBLE); -//// } -// } - } - - - public void onLoginClicked(View view) { - mAccountID = idEditText.getText().toString(); - mAccountPD = pdEditText.getText().toString(); - - if(mAccountID==null) { - ToastUtil.showLong(getString(R.string.tips_login_failure)); - return; - }else if(mAccountID.length()<4) - if(!mAccountID.contains("@") || mAccountID.contains(" ")) { - ToastUtil.showLong(getString(R.string.tips_login_id_is_error)); - return; - } - if(mAccountPD==null) { - ToastUtil.showLong(getString(R.string.tips_login_pd_is_empty)); - return; - }else if(mAccountID.length() < 4) { - ToastUtil.showLong(getString(R.string.tips_login_pd_is_error)); - return; - } - - startLogin(); - KLog.i("【handler】" + "-"); - } - - - private void startLogin() { - loginButton.setEnabled(false); - idEditText.setEnabled(false); - pdEditText.setEnabled(false); - - Intent intent = new Intent(this, MainService.class); - intent.setAction(Api.LOGIN); - intent.putExtra("accountID", mAccountID); - intent.putExtra("accountPW", mAccountPD); - startService(intent); - } - - public void clickRegister(View view) { - Intent intent = new Intent(LoginActivity.this, WebActivity.class); - intent.setData(Uri.parse("https://www.inoreader.com/?lang=" + Locale.getDefault())); - startActivity(intent); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - } - -// private LocalBroadcastManager manager; -// private BroadcastReceiver localReceiver; -// private void initBroadcast() { -// manager = LocalBroadcastManager.getInstance(this); -// // 先创建一个 BroadcastReceiver 实例 -// localReceiver = new BroadcastReceiver() { -// @Override -// public void onReceive(Context context, Intent intent) { -// String data = intent.getStringExtra(Api.NOTICE); -// } -// }; -// -// // 动态注册这个 receiver 实例,记得在不需要时注销 // Api.SYNC_ALL -// manager.registerReceiver(localReceiver, new IntentFilter(Api.SYNC_ALL)); -// } - - - public void onSBClick(View view){ - SwitchButton v = (SwitchButton)view; - KLog.d( "点击" ); - switch (v.getId()) { - case R.id.setting_proxy_sb: - WithPref.i().setInoreaderProxy(v.isChecked()); - break; - } - KLog.i("Switch: ", v.isChecked() ); - } - - -} diff --git a/app/src/main/java/me/wizos/loread/activity/MainActivity.java b/app/src/main/java/me/wizos/loread/activity/MainActivity.java index 4457196..7a829df 100644 --- a/app/src/main/java/me/wizos/loread/activity/MainActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/MainActivity.java @@ -1,5 +1,6 @@ package me.wizos.loread.activity; +import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; @@ -7,217 +8,262 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.support.design.widget.BottomSheetBehavior; -import android.support.design.widget.BottomSheetDialog; -import android.support.v7.widget.Toolbar; import android.text.InputType; -import android.util.ArrayMap; import android.view.KeyEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.CompoundButton; -import android.widget.ExpandableListView; +import android.widget.ImageView; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearSmoothScroller; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; +import androidx.work.Constraints; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; + import com.afollestad.materialdialogs.MaterialDialog; +import com.afollestad.materialdialogs.simplelist.MaterialSimpleListAdapter; +import com.afollestad.materialdialogs.simplelist.MaterialSimpleListItem; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.hjq.permissions.OnPermission; +import com.hjq.permissions.Permission; +import com.hjq.permissions.XXPermissions; +import com.hjq.toast.ToastUtils; +import com.jeremyliao.liveeventbus.LiveEventBus; import com.kyleduo.switchbutton.SwitchButton; -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.model.Response; +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.enums.PopupAnimation; +import com.lxj.xpopup.interfaces.OnSelectListener; import com.socks.library.KLog; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; +import com.yanzhenjie.recyclerview.OnItemClickListener; +import com.yanzhenjie.recyclerview.OnItemLongClickListener; +import com.yanzhenjie.recyclerview.OnItemMenuClickListener; +import com.yanzhenjie.recyclerview.OnItemSwipeListener; +import com.yanzhenjie.recyclerview.SwipeMenu; +import com.yanzhenjie.recyclerview.SwipeMenuBridge; +import com.yanzhenjie.recyclerview.SwipeMenuCreator; +import com.yanzhenjie.recyclerview.SwipeMenuItem; +import com.yanzhenjie.recyclerview.SwipeRecyclerView; + +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import butterknife.ButterKnife; import butterknife.OnClick; import me.wizos.loread.App; import me.wizos.loread.R; -import me.wizos.loread.adapter.ExpandableListAdapterS; -import me.wizos.loread.adapter.MainListViewAdapter; -import me.wizos.loread.adapter.MaterialSimpleListAdapter; -import me.wizos.loread.adapter.MaterialSimpleListItem; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.data.WithPref; +import me.wizos.loread.adapter.ArticlePagedListAdapter; +import me.wizos.loread.adapter.ExpandedAdapter; import me.wizos.loread.db.Article; -import me.wizos.loread.db.Feed; -import me.wizos.loread.db.Tag; -import me.wizos.loread.event.Sync; -import me.wizos.loread.net.Api; -import me.wizos.loread.net.DataApi; -import me.wizos.loread.net.InoApi; -import me.wizos.loread.service.MainService; -import me.wizos.loread.utils.NetworkUtil; -import me.wizos.loread.utils.ScreenUtil; +import me.wizos.loread.db.Collection; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.network.SyncWorker; +import me.wizos.loread.network.callback.CallbackX; import me.wizos.loread.utils.SnackbarUtil; -import me.wizos.loread.utils.StringUtil; -import me.wizos.loread.utils.ToastUtil; -import me.wizos.loread.view.ExpandableListViewS; +import me.wizos.loread.utils.TimeUtil; import me.wizos.loread.view.IconFontView; -import me.wizos.loread.view.ListView.ListViewS; import me.wizos.loread.view.SwipeRefreshLayoutS; import me.wizos.loread.view.colorful.Colorful; import me.wizos.loread.view.colorful.setter.ViewGroupSetter; -import okhttp3.FormBody; +import me.wizos.loread.viewmodel.ArticleViewModel; -//import com.zhangyue.we.x2c.X2C; -//import com.zhangyue.we.x2c.ano.Xml; /** - * @author Wizos on 2016 + * @author Wizos on 2016‎年5‎月23‎日 */ public class MainActivity extends BaseActivity implements SwipeRefreshLayoutS.OnRefreshListener { - protected static final String TAG = "MainActivity"; + private static final String TAG = "MainActivity"; private IconFontView vPlaceHolder; - private TextView vToolbarHint; + private ImageView vToolbarAutoMark; private Toolbar toolbar; private SwipeRefreshLayoutS swipeRefreshLayoutS; - private ListViewS articleListView; - private MainListViewAdapter articleListAdapter; + private SwipeRecyclerView articleListView; + // private MultiTypeAdapter articlesAdapter; + private ArticlePagedListAdapter articlesAdapter; private IconFontView refreshIcon; - private ExpandableListViewS tagListView; - private ExpandableListAdapterS tagListAdapter; - private View headerPinnedView; -// private int tagCount; -// private View headerHomeView; -// private ImageLoader imageLoader; -// private boolean isFirstIn = true; -// private int firstVisibleItemPosition; -// private int lastVisibleItemPosition; + // 方案3 + private SwipeRecyclerView tagListView; + private ExpandedAdapter tagListAdapter; + + private TextView countTips; + + private Integer[] scrollIndex; + private View articlesHeaderView; + + private BottomSheetDialog quickSettingDialog; + private BottomSheetDialog tagBottomSheetDialog; + private RelativeLayout relativeLayout; + //private StickyHeaderLayout stickyHeaderLayout; + private boolean autoMarkReaded = false; + private static Handler maHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ButterKnife.bind(this); - EventBus.getDefault().register(this); initToolbar(); initIconView(); initArtListView(); initTagListView(); initSwipeRefreshLayout(); // 必须要放在 initArtListView() 之后,不然无论 ListView 滚动到第几页,一下拉就会触发刷新 - initData(); // 获取文章列表数据为 App.articleList -// KLog.i("列表数目:" + App.articleList.size() + " 当前状态:" + App.StreamState); - autoMarkReaded = WithPref.i().isScrollMark(); - maHandler.postDelayed(heartbeatTask, WithPref.i().getAutoSyncFrequency() * 60000); - -// if (savedInstanceState != null) { -// final int position = savedInstanceState.getInt("listItemFirstVisiblePosition"); -// slvSetSelection(position); -// } - Intent intent = getIntent(); - if ("firstSetupStart".equals(intent.getAction())) { - startSyncService(Api.SYNC_ALL); - } showAutoSwitchThemeSnackBar(); - super.onCreate(savedInstanceState); + applyPermissions(); + super.onCreate(savedInstanceState);// 由于使用了自动换主题,所以要放在这里 + getArtData(); // 获取文章列表数据为 App.articleList + autoMarkReaded = App.i().getUser().isMarkReadOnScroll(); + initWorkRequest(); } + private void initWorkRequest(){ +// Constraints.Builder builder = new Constraints.Builder(); +// if(App.i().getUser().isAutoSync()){ +// if( App.i().getUser().isAutoSyncOnlyWifi() ){ +// builder.setRequiredNetworkType(NetworkType.UNMETERED); +// }else { +// builder.setRequiredNetworkType(NetworkType.CONNECTED); +// } +// PeriodicWorkRequest syncRequest = new PeriodicWorkRequest.Builder(SyncWorker.class, App.i().getUser().getAutoSyncFrequency(), TimeUnit.MINUTES) +// .setConstraints(builder.build()) +// .addTag(SyncWorker.TAG) +// .build(); +// WorkManager.getInstance(this).enqueueUniquePeriodicWork(SyncWorker.TAG, ExistingPeriodicWorkPolicy.KEEP,syncRequest); +// KLog.i("SyncWorker Id: " + syncRequest.getId()); +// } -// @Override -// protected void onSaveInstanceState(Bundle outState) { -// outState.putInt("listItemFirstVisiblePosition", articleListView.getFirstVisiblePosition()); -// super.onSaveInstanceState(outState); -// } - + Constraints.Builder builder = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED); + PeriodicWorkRequest syncRequest = new PeriodicWorkRequest.Builder(SyncWorker.class, App.i().getUser().getAutoSyncFrequency(), TimeUnit.MINUTES) + .setConstraints(builder.build()) + .addTag(SyncWorker.TAG) + .build(); + WorkManager.getInstance(this).enqueueUniquePeriodicWork(SyncWorker.TAG, ExistingPeriodicWorkPolicy.KEEP,syncRequest); + KLog.i("SyncWorker Id: " + syncRequest.getId()); - @Override - protected void onResume(){ - super.onResume(); - if (articleListAdapter != null) { - articleListAdapter.notifyDataSetChanged(); -// KLog.e("通知articleList数据有变化"); + if(App.i().getUser().isAutoSync()){ } -// tagCount = UnreadCountUtil.getUnreadCount(App.StreamId); -// tagCount = App.articleList.size(); -// KLog.i("【onResume】" + App.StreamState + "---" + toolbar.getTitle() + "===" + App.StreamId); + + LiveEventBus.get(SyncWorker.SYNC_TASK_STATUS,Boolean.class) + .observeSticky(this, new Observer() { + @Override + public void onChanged(Boolean isSyncing) { + KLog.e("任务状态:" + isSyncing ); + swipeRefreshLayoutS.setRefreshing(false); + if(isSyncing){ + swipeRefreshLayoutS.setEnabled(false); + }else { + swipeRefreshLayoutS.setEnabled(true); + } + } + }); + LiveEventBus + .get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE, String.class) + .observe(this, new Observer() { + @Override + public void onChanged(@Nullable String tips) { + toolbar.setSubtitle( tips ); + } + }); + LiveEventBus.get(SyncWorker.NEW_ARTICLE_NUMBER,Integer.class) + .observe(this, new Observer() { + @Override + public void onChanged(Integer integer) { + if(integer == 0){ + return; + } + SnackbarUtil.Long(articleListView,bottomBar, getResources().getQuantityString(R.plurals.has_new_articles,integer,integer) ) + .setAction(getString(R.string.view), new View.OnClickListener() { + @Override + public void onClick(View v) { + refreshData(); + } + }).show(); + refreshIcon.setVisibility(View.VISIBLE); + } + }); } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onReceiveSyncResult(Sync sync) { - int result = sync.result; -// KLog.e( "接收到的数据为:"+ result ); - switch (result) { - case Sync.START: - swipeRefreshLayoutS.setRefreshing(true); - swipeRefreshLayoutS.setEnabled(false); - break; - case Sync.END: - swipeRefreshLayoutS.setRefreshing(false); - swipeRefreshLayoutS.setEnabled(true); - toolbar.setSubtitle(null); - SnackbarUtil.Long(articleListView, "有新文章") - .setAction("查看", new View.OnClickListener() { - @Override - public void onClick(View v) { - refreshData(); - IconFontView loadNewArticlesIcon = findViewById(R.id.main_bottombar_refresh_articles); - loadNewArticlesIcon.setVisibility(View.GONE); - } - }).show(); - IconFontView loadNewArticlesIcon = findViewById(R.id.main_bottombar_refresh_articles); - loadNewArticlesIcon.setVisibility(View.VISIBLE); - break; - // 文章获取失败 - case Sync.ERROR: - swipeRefreshLayoutS.setRefreshing(false); - swipeRefreshLayoutS.setEnabled(true); - toolbar.setSubtitle(null); -// vToolbarHint.setText(String.valueOf(tagCount)); - break; - case Sync.DOING: -// KLog.e("接受的文字:" + sync.notice); - toolbar.setSubtitle(sync.notice); - break; - default: - toolbar.setSubtitle(null); - break; - } + private void applyPermissions() { + XXPermissions.with(this) + //.constantRequest() //可设置被拒绝后继续申请,直到用户授权或者永久拒绝 + //.permission(Permission.SYSTEM_ALERT_WINDOW) //支持请求6.0悬浮窗权限8.0请求安装权限 + .permission(Permission.Group.STORAGE) //不指定权限则自动获取清单中的危险权限 + .request(new OnPermission() { + @Override + public void hasPermission(List granted, boolean isAll) { + } + + @Override + public void noPermission(List denied, boolean quick) { + for (String id : denied) { + KLog.e("无法获取权限" + id); + } + ToastUtils.show(getString(R.string.plz_grant_permission_tips)); + } + }); } - private void showAutoSwitchThemeSnackBar() { - if (App.hadAutoToggleTheme) { - SnackbarUtil.Long(articleListView, "已自动切换主题") -// .above(bt_gravity_center,total,16,16) - .setAction("撤销", new View.OnClickListener() { - @Override - public void onClick(View v) { - manualToggleTheme(); - } - }).show(); - App.hadAutoToggleTheme = false; + @Override + protected void onResume() { + super.onResume(); + if (articlesAdapter != null) { + articlesAdapter.notifyDataSetChanged(); } } - - Runnable heartbeatTask = new Runnable() { - @Override - public void run() { - if (!WithPref.i().isAutoSync()) { - return; - } - if (WithPref.i().isAutoSyncOnWifi() && !NetworkUtil.isWiFiUsed()) { - return; - } - startSyncService(Api.SYNC_HEARTBEAT); - maHandler.postDelayed(this, WithPref.i().getAutoSyncFrequency() * 60000); + private void showAutoSwitchThemeSnackBar() { + if (!App.i().getUser().isAutoToggleTheme()) { + return; } - }; + int hour = TimeUtil.getCurrentHour(); + int themeMode; + if (hour >= 7 && hour < 20) { + themeMode = App.THEME_DAY; + } else { + themeMode = App.THEME_NIGHT; + } + if (App.i().getUser().getThemeMode() == themeMode) { + return; + } + + SnackbarUtil.Long(articleListView, bottomBar, getString(R.string.theme_switched_automatically)) + .setAction(getString(R.string.cancel), new View.OnClickListener() { + @Override + public void onClick(View v) { + manualToggleTheme(); + } + }).show(); + } protected void initIconView() { - vToolbarHint = findViewById(R.id.main_toolbar_hint); + vToolbarAutoMark = findViewById(R.id.main_toolbar_auto_mark); + if (App.i().getUser().isMarkReadOnScroll()) { + vToolbarAutoMark.setVisibility(View.VISIBLE); + } vPlaceHolder = findViewById(R.id.main_placeholder); refreshIcon = findViewById(R.id.main_bottombar_refresh_articles); } @@ -245,779 +291,684 @@ public void onRefresh() { return; } KLog.i("【刷新中】"); - startSyncService(Api.SYNC_ALL); - swipeRefreshLayoutS.setRefreshing(true); - swipeRefreshLayoutS.setEnabled(false); + Constraints.Builder builder = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED); + OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(SyncWorker.class) + .setConstraints(builder.build()) + .addTag(SyncWorker.TAG) + .build(); + WorkManager.getInstance(this).enqueue(oneTimeWorkRequest); } // 按下back键时会调用onDestroy()销毁当前的activity,重新启动此activity时会调用onCreate()重建; // 而按下home键时会调用onStop()方法,并不销毁activity,重新启动时则是调用onResume() @Override protected void onDestroy() { - // 如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。 - // 这样做的好处是在Acticity退出的时候,可以避免内存泄露。因为 handler 内可能引用 Activity ,导致 Activity 退出后,内存泄漏。 + // 参数为null,会将所有的Callbacks和Messages全部清除掉。 + // 这样做的好处是在Activity退出的时候,可以避免内存泄露。因为 handler 内可能引用 Activity maHandler.removeCallbacksAndMessages(null); - EventBus.getDefault().unregister(this); + //EventBus.getDefault().unregister(this); super.onDestroy(); } - - private void startSyncService(String action) { - Intent intent = new Intent(this, MainService.class); - intent.setAction(action); - startService(intent); - KLog.i("调用 SubService,开始 SYNC_ALL"); - } - - - // 这里不必和 ArticleActivity 一样写成静态内部类,用来防止因持有外部的Activity而造成内存泄漏。 - // 因为MainActivity基本只有一个实例,且不会反复创建、销毁,所以不用担心回收造成的内存泄漏问题。 - private Handler maHandler = new Handler(); - /** * App.StreamState 包含 3 个状态:All,Unread,Stared - * App.StreamId 至少包含 1 个状态: Reading-list - * */ + * App.streamId 至少包含 1 个状态: Reading-list + */ protected void refreshData() { // 获取 App.articleList , 并且根据 App.articleList 的到未读数目 - KLog.e("refreshData:" + App.StreamId + " " + " - " + App.StreamStatus + " " + App.UserID); - // 取消所有下载图片的任务,防止下载后去加载到imageview中 -// imageLoader.cancelAllLoadTask(); -// isFirstIn = true; + //KLog.e("refreshData:" + App.i().getUser().getStreamId() + " = " + App.i().getUser().getStreamStatus() + " " + App.i().getUser().getUserId()); getArtData(); - articleListAdapter = new MainListViewAdapter(this, App.articleList, articleListView); - articleListView.setAdapter(articleListAdapter); -// getTagData(); -// tagListAdapter.notifyDataSetChanged(); - loadViewByData(); refreshIcon.setVisibility(View.GONE); } - private void initData() { - getArtData(); - articleListAdapter = new MainListViewAdapter(this, App.articleList, articleListView); - articleListView.setAdapter(articleListAdapter); - getTagData(); - tagListAdapter = new ExpandableListAdapterS(this, App.tagList, tagListView); - tagListView.setAdapter(tagListAdapter); - loadViewByData(); - } - - @SuppressWarnings("unchecked") + private ArticleViewModel articleViewModel; private void getArtData() { - if (App.StreamId.startsWith("user/")) { - if (App.StreamId.contains(Api.U_READING_LIST)) { - if (App.StreamStatus == Api.STARED) { - App.articleList = WithDB.i().getArtsStared(); - } else if (App.StreamStatus == Api.UNREAD) { - App.articleList = WithDB.i().getArtsUnread(); - } else { - App.articleList = WithDB.i().getArtsAll(); - } - } else if (App.StreamId.contains(Api.U_NO_LABEL)) { - if (App.StreamStatus == Api.STARED) { - App.articleList = WithDB.i().getArtsStaredNoTag(); - } else if (App.StreamStatus == Api.UNREAD) { - App.articleList = WithDB.i().getArtsUnreadNoTag(); - } else { - App.articleList = WithDB.i().getArtsAllNoTag(); - } - } else { - // TEST: 测试 - Tag theTag = WithDB.i().getTag(App.StreamId); - if (App.StreamStatus == Api.STARED) { - App.articleList = WithDB.i().getArtsStaredInTag(theTag); - } else if (App.StreamStatus == Api.UNREAD) { - App.articleList = WithDB.i().getArtsUnreadInTag(theTag); - } else { - App.articleList = WithDB.i().getArtsAllInTag(theTag); - } - } - } else if (App.StreamId.startsWith("feed/")) { - if (App.StreamStatus == Api.STARED) { - App.articleList = WithDB.i().getArtsStaredInFeed(App.StreamId); - } else if (App.StreamStatus == Api.UNREAD) { - App.articleList = WithDB.i().getArtsUnreadInFeed(App.StreamId); - } else { - App.articleList = WithDB.i().getArtsAllInFeed(App.StreamId); - } - App.StreamTitle = WithDB.i().getFeed(App.StreamId).getTitle(); + String uid = App.i().getUser().getId(); + int streamStatus = App.i().getUser().getStreamStatus(); + int streamType = App.i().getUser().getStreamType(); + String streamId = App.i().getUser().getStreamId(); + articlesAdapter.setLastPos(linearLayoutManager.findLastVisibleItemPosition()-1); + + if(articleViewModel.articles != null && articleViewModel.articles.hasObservers()){ + articleViewModel.articles.removeObservers(this); + articleViewModel.articles = null; } - startSyncService(Api.CLEAR); - } - - private void getTagData() { - long time = System.currentTimeMillis(); - Tag rootTag = new Tag(); - Tag noLabelTag = new Tag(); - long userID = WithPref.i().getUseId(); - rootTag.setTitle(getString(R.string.main_activity_title_all)); - noLabelTag.setTitle(getString(R.string.main_activity_title_untag)); - - rootTag.setId("user/" + userID + Api.U_READING_LIST); - rootTag.setSortid("00000000"); - rootTag.__setDaoSession(App.i().getDaoSession()); - - noLabelTag.setId("user/" + userID + Api.U_NO_LABEL); - noLabelTag.setSortid("00000001"); - noLabelTag.__setDaoSession(App.i().getDaoSession()); - - List tagListTemp = new ArrayList<>(); - tagListTemp.add(rootTag); - tagListTemp.add(noLabelTag); - tagListTemp.addAll(WithDB.i().getTagsWithUnreadCount()); - - App.i().updateTagList(tagListTemp); - KLog.e("加载tag耗时:" + (System.currentTimeMillis() - time)); + articleViewModel.getArticles(uid,streamId,streamType,streamStatus).observe(this, new Observer>() { + @Override + public void onChanged(PagedList
articles) { +// if( articlesAdapter.getCurrentList() != null ){ +// KLog.e("更新列表数据 A : " + articlesAdapter.getCurrentList().getLastKey() + " , " + (linearLayoutManager.findLastVisibleItemPosition()-1) ); +// } +// if( articlesAdapter.getCurrentList() != null ){ +// KLog.e("更新列表数据 B : " + articlesAdapter.getCurrentList().getLastKey() + " , " + (linearLayoutManager.findLastVisibleItemPosition()-1) ); +// } + articlesAdapter.submitList(articles); +// if( articlesAdapter.getCurrentList() != null ){ +// KLog.e("更新列表数据 C : " + articlesAdapter.getCurrentList().getLastKey() + " , " + (linearLayoutManager.findLastVisibleItemPosition()-1) ); +// } + loadViewByData( articles.size() ); + } + }); + articleListView.scrollToPosition(0); + // KLog.e("更新列表数据 A , " + articlesAdapter.getItemCount() ); } + private void loadViewByData(int size) { + // 在setSupportActionBar(toolbar)之后调用toolbar.setTitle()的话。 在onCreate()中调用无效。在onStart()中调用无效。 在onResume()中调用有效。 + getSupportActionBar().setTitle(App.i().getUser().getStreamTitle()); + countTips.setText( getResources().getQuantityString(R.plurals.articles_count, size, size ) ); - private void loadViewByData() { -// KLog.i("【】" + App.StreamState + "--" + App.StreamTitle + "--" + App.StreamId + "--" + toolbar.getTitle() + App.articleList.size()); - if (StringUtil.isBlank(App.articleList)) { +// KLog.i("【loadViewByData】" + App.i().getUser().getStreamId()+ "--" + App.i().getUser().getStreamTitle() + "--" + App.i().getUser().getStreamStatus() + "--" + toolbar.getTitle() + articlesAdapter.getItemCount()); + if (articlesAdapter == null || articlesAdapter.getItemCount() == 0) { vPlaceHolder.setVisibility(View.VISIBLE); articleListView.setVisibility(View.GONE); - }else { + } else { vPlaceHolder.setVisibility(View.GONE); articleListView.setVisibility(View.VISIBLE); } - - // 每次重新加载列表数据的时候,应该把 ArticleListAdapter 和 TagListAdapter 都更新一下。 - // adapter中的数据源集合或数组等必须是同一个数据源,也就是同一个对象。 - // 当数据源发生变化的时候,我们会调用adaper的notifyDataSetChanged()方法。 - // 当直接将从数据库或者其他方式获取的数据源集合或者数组直接赋值给当前数据源时,相当于当前数据源的对象发生了变化。 - // 当前对象已经不是adapter中的对象了,所以adaper调用notifyDataSetChanged()方法不会进行刷新数据和界面的操作。 - KLog.e("loadViewByData" + "=" + "=" + App.tagList.size()); - // 在setSupportActionBar(toolbar)之后调用toolbar.setTitle()的话。 在onCreate()中调用无效。在onStart()中调用无效。 在onResume()中调用有效。 - // toolbar.setTitle(App.StreamTitle); - getSupportActionBar().setTitle(App.StreamTitle); -// toolbar.setSubtitle(null); -// KLog.e("loadViewByData","此时StreamId为:" + App.StreamId + " 此时 Title 为:" + App.StreamTitle ); - -// tagCount = UnreadCountUtil.getUnreadCount(App.StreamId); -// tagCount = App.articleList.size(); -// vToolbarHint.setText(String.valueOf(tagCount)); } - public void showTagDialog(final Tag tag) { + public void showTagDialog(final Collection category) { // 重命名弹窗的适配器 - MaterialSimpleListAdapter adapter = new MaterialSimpleListAdapter(MainActivity.this); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content(R.string.main_tag_dialog_rename) - .icon(R.drawable.dialog_ic_rename) + MaterialSimpleListAdapter adapter = new MaterialSimpleListAdapter(new MaterialSimpleListAdapter.Callback() { + @Override + public void onMaterialListItemSelected(MaterialDialog dialog, int index, MaterialSimpleListItem item) { + if (index == 0) { + new MaterialDialog.Builder(MainActivity.this) + .title(R.string.edit_name) + .inputType(InputType.TYPE_CLASS_TEXT) + .inputRange(1, 22) + .input(null, category.getTitle(), new MaterialDialog.InputCallback() { + @Override + public void onInput(@NotNull MaterialDialog dialog, CharSequence input) { + renameTag(input.toString(), category); + } + }) + .positiveText(R.string.confirm) + .negativeText(android.R.string.cancel) + .show(); + } + dialog.dismiss(); + } + }); + adapter.add(new com.afollestad.materialdialogs.simplelist.MaterialSimpleListItem.Builder(MainActivity.this) + .content(R.string.rename) + .icon(R.drawable.ic_rename) .backgroundColor(Color.TRANSPARENT) .build()); + new MaterialDialog.Builder(MainActivity.this) - .adapter(adapter, new MaterialDialog.ListCallback() { - @Override - public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { - if (which == 0) { - new MaterialDialog.Builder(MainActivity.this) - .title(R.string.rename) - .inputType(InputType.TYPE_CLASS_TEXT) - .inputRange(1, 22) - .input(null, tag.getTitle(), new MaterialDialog.InputCallback() { - @Override - public void onInput(MaterialDialog dialog, CharSequence input) { - renameTag(input.toString(), tag); - } - }) - .positiveText(R.string.confirm) - .negativeText(android.R.string.cancel) - .show(); - } - dialog.dismiss(); - } - }) + .adapter(adapter, new LinearLayoutManager(MainActivity.this)) .show(); } - public void renameTag(final String renamedTagTitle, Tag tag) { - KLog.e("=====" + renamedTagTitle); - if (renamedTagTitle.equals("") || tag.getTitle().equals(renamedTagTitle)) { + public void renameTag(final String renamedTagTitle, Collection category) { + KLog.i("renameTag", renamedTagTitle); + if (renamedTagTitle.equals("") || category.getTitle().equals(renamedTagTitle)) { return; } - final String destTagId = tag.getId().replace(tag.getTitle(), renamedTagTitle); - final String sourceTagId = tag.getId(); - DataApi.i().renameTag(sourceTagId, destTagId, new StringCallback() { + final String newCategoryId = "user/" + App.i().getUser().getUserId() + "/" + renamedTagTitle; + final String oldCategoryId = category.getId(); + App.i().getApi().renameTag(oldCategoryId, renamedTagTitle, new CallbackX() { @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; - } - Tag tag = WithDB.i().getTag(sourceTagId); - if (tag == null) { - this.onError(response); - return; - } - WithDB.i().delTag(tag); - tag.setId(destTagId); - tag.setTitle(renamedTagTitle); - WithDB.i().insertTag(tag); - WithDB.i().updateFeedsCategoryId(sourceTagId, destTagId); // 由于改 tag 的名字,涉及到改 tag 的 id ,而每个 feed 自带的 tag 的 id 也得改过来。 + public void onSuccess(String result) { + CoreDB.i().categoryDao().updateId(App.i().getUser().getId(),oldCategoryId,newCategoryId); + CoreDB.i().feedCategoryDao().updateCategoryId(App.i().getUser().getId(),oldCategoryId,newCategoryId); tagListAdapter.notifyDataSetChanged(); } @Override - public void onError(Response response) { - ToastUtil.showLong(getString(R.string.toast_rename_fail)); + public void onFailure(String error) { + ToastUtils.show(getString(R.string.rename_failed)); } }); } - - public void showFeedDialog(final ExpandableListAdapterS.ItemViewHolder itemView, final Feed feed) { + public void showFeedActivity(final int parentPosition, final int childPosition) { + Collection feed = tagListAdapter.getChild(parentPosition, childPosition); if (feed == null) { return; } - // 重命名弹窗的适配器 - MaterialSimpleListAdapter adapter = new MaterialSimpleListAdapter(MainActivity.this); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content(R.string.main_tag_dialog_rename) -// .icon(R.drawable.dialog_ic_rename) - .backgroundColor(Color.TRANSPARENT) - .build()); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content("修改分组") -// .icon(R.drawable.dialog_ic_unsubscribe) - .backgroundColor(Color.TRANSPARENT) - .build()); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content(R.string.main_tag_dialog_unsubscribe) -// .icon(R.drawable.dialog_ic_unsubscribe) - .backgroundColor(Color.TRANSPARENT) - .build()); - final ArrayList tags = WithDB.i().getTagsWithUnreadCount(); - final ArrayList titles = new ArrayList<>(tags.size()); - int selectedIndex = -1; - + Intent intent = new Intent(MainActivity.this, FeedActivity.class); + intent.putExtra("feedId", feed.getId()); + startActivity(intent); + } - for (int i = 0, size = tags.size(); i < size; i++) { - titles.add(tags.get(i).getTitle()); - if (tags.get(i).getId().equals(feed.getCategoryid())) { - selectedIndex = i; - } - } - final Integer[] preSelectedIndices; - if (selectedIndex != -1) { - preSelectedIndices = new Integer[]{selectedIndex}; - } else { - preSelectedIndices = null; - } + LinearLayoutManager linearLayoutManager; + public void initArtListView() { + articleListView = findViewById(R.id.main_slv); + linearLayoutManager = new LinearLayoutManager(this); + articleListView.setLayoutManager(linearLayoutManager); + // HeaderView。 + articlesHeaderView = getLayoutInflater().inflate(R.layout.main_item_header, articleListView, false); + countTips = (TextView) articlesHeaderView.findViewById(R.id.main_header_title); + ImageView eye = articlesHeaderView.findViewById(R.id.main_header_eye); + eye.setOnClickListener(v -> ToastUtils.show(R.string.display_filter_in_development) ); + articleListView.addHeaderView(articlesHeaderView); - new MaterialDialog.Builder(MainActivity.this) - .title("配置该源") - .content(feed.getUrl()) - .adapter(adapter, new MaterialDialog.ListCallback() { - @Override - public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { - switch (which) { - case 0: - new MaterialDialog.Builder(MainActivity.this) - .title(R.string.rename) - .inputType(InputType.TYPE_CLASS_TEXT) - .inputRange(1, 22) - .input(null, feed.getTitle(), new MaterialDialog.InputCallback() { - @Override - public void onInput(MaterialDialog dialog, CharSequence input) { - renameFeed(input.toString(), feed); - } - }) - .positiveText(R.string.confirm) - .negativeText(android.R.string.cancel) - .show(); - break; - case 1: - new MaterialDialog.Builder(MainActivity.this) - .title("修改分组") - .items(titles) - .itemsCallbackMultiChoice(preSelectedIndices, new MaterialDialog.ListCallbackMultiChoice() { - @Override - public boolean onSelection(MaterialDialog dialog, final Integer[] which, CharSequence[] text) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("ac", "edit"); - builder.add("s", feed.getId()); - if (which.length == 0) { - builder.add("r", feed.getCategoryid()); - } else { - builder.add("a", tags.get(which[0]).getId()); + articleListView.setOnItemLongClickListener(new OnItemLongClickListener() { + @Override + public void onItemLongClick(View view, final int position) { + new XPopup.Builder(MainActivity.this) + .isCenterHorizontal(true) //是否与目标水平居中对齐 + .offsetY(-view.getHeight() / 2) + .hasShadowBg(true) + .popupAnimation(PopupAnimation.ScaleAlphaFromCenter) + .atView(view) // 依附于所点击的View,内部会自动判断在上方或者下方显示 + .asAttachList( + new String[]{getString(R.string.speak_article), getString(R.string.mark_up), getString(R.string.mark_down), getString(R.string.mark_unread)}, + new int[]{R.drawable.ic_volume, R.drawable.ic_mark_up, R.drawable.ic_mark_down, R.drawable.ic_mark_unread}, + new OnSelectListener() { + @Override + public void onSelect(int which, String text) { + switch (which) { + case 0: + Intent intent = new Intent(MainActivity.this,TTSActivity.class); + intent.putExtra("articleNo",position); + intent.putExtra("isQueue",true); + startActivity(intent); + break; + case 1: + Integer[] index = new Integer[2]; + index[0] = position + 1; + index[1] = 0; + new MarkListReadedAsyncTask().execute(index); + break; + case 2: + showConfirmDialog(position,articlesAdapter.getItemCount()); + break; + case 3: + Article article = articlesAdapter.getItem(position); +// Article article = CoreDB.i().articleDao().getById(App.i().getUser().getId(),articlesAdapter.getItem(position).getId()); + if( article == null ){ + return; } - DataApi.i().editFeed(builder, new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; - } - if (which != null && which.length == 0) { - feed.setCategoryid(""); - feed.setCategorylabel(""); - } else { - feed.setCategoryid(tags.get(which[0]).getId()); - feed.setCategorylabel(tags.get(which[0]).getTitle()); + if (article.getReadStatus() == App.STATUS_READED) { + int oldReadStatus = article.getReadStatus(); + App.i().getApi().markArticleUnread(article.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { } - feed.update(); - tagListAdapter.removeChild(itemView.groupPos, feed); - tagListAdapter.notifyDataSetChanged(); - ToastUtil.showLong("修改分组成功!"); - } - - @Override - public void onError(Response response) { - ToastUtil.showLong(App.i().getString(R.string.toast_rename_fail)); - } - }); - return which.length <= 1; - } - }) - .alwaysCallMultiChoiceCallback() // the callback will always be called, to check if selection is still allowed - .show(); - break; - case 2: - DataApi.i().unsubscribeFeed(feed.getId(), new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; + @Override + public void onFailure(Object error) { + article.setReadStatus(oldReadStatus); + CoreDB.i().articleDao().update(article); + } + }); + } + article.setReadStatus(App.STATUS_UNREADING); + CoreDB.i().articleDao().update(article); + articlesAdapter.notifyItemChanged(position); + break; + default: + break; } - WithDB.i().delFeed(feed); -// KLog.e("移除" + itemView.groupPos + " " + itemView.childPos ); -// tagListAdapter.removeChild(itemView.groupPos, itemView.childPos); - tagListAdapter.removeChild(itemView.groupPos, feed); - tagListAdapter.notifyDataSetChanged(); - } - - @Override - public void onError(Response response) { - ToastUtil.showLong(getString(R.string.toast_unsubscribe_fail)); } - }); - break; - default: - break; - } - dialog.dismiss(); - } - }) - .show(); - } + }) + .show(); + } + }); - public void renameFeed(final String renamedTitle, final Feed feed) { - final String feedId = feed.getId(); - KLog.e("=====" + renamedTitle + feedId); - if (renamedTitle.equals("") || feed.getTitle().equals(renamedTitle)) { - return; - } - DataApi.i().renameFeed(feedId, renamedTitle, new StringCallback() { + articleListView.addOnScrollListener(new RecyclerView.OnScrollListener() { + // 正在被外部拖拽,一般为用户正在用手指滚动 SCROLL_STATE_DRAGGING,自动滚动 SCROLL_STATE_SETTLING,正在滚动(SCROLL_STATE_IDLE) @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + articlesAdapter.setLastPos(linearLayoutManager.findLastVisibleItemPosition()-1); + //KLog.i("【滚动】" + ((RecyclerView.LayoutParams) recyclerView.getChildAt(1).getLayoutParams()).getViewAdapterPosition() + " = "+ linearLayoutManager.findFirstVisibleItemPosition() + " , " + linearLayoutManager.findLastVisibleItemPosition()); + if (!autoMarkReaded) { return; } -// Feed feed = WithDB.i().getFeed(feedId); -// if (feed == null) { -// this.onError(response); -// return; -// } - feed.setTitle(renamedTitle); - feed.update(); -// WithDB.i().updateFeed(feed); - // 由于改了 feed 的名字,而每个 article 自带的 feed 名字也得改过来。 - WithDB.i().updateArtsFeedTitle(feed); - tagListAdapter.notifyDataSetChanged(); - } - - @Override - public void onError(Response response) { - ToastUtil.showLong(getString(R.string.toast_rename_fail)); + // || RecyclerView.SCROLL_STATE_SETTLING == newState + //KLog.i("滚动:" + newState); + if (RecyclerView.SCROLL_STATE_DRAGGING == newState && scrollIndex == null) { + scrollIndex = new Integer[2]; + scrollIndex[0] = ((RecyclerView.LayoutParams) recyclerView.getChildAt(0).getLayoutParams()).getViewAdapterPosition(); + KLog.i("滚动开始:" + scrollIndex[0] + " = "+ linearLayoutManager.findFirstVisibleItemPosition() ); + } else if (RecyclerView.SCROLL_STATE_IDLE == newState && scrollIndex != null) { + scrollIndex[1] = ((RecyclerView.LayoutParams) recyclerView.getChildAt(0).getLayoutParams()).getViewAdapterPosition(); + new MarkListReadedAsyncTask().execute(scrollIndex); + KLog.i("滚动结束:" + scrollIndex[1] + " = "+ linearLayoutManager.findFirstVisibleItemPosition() ); + scrollIndex = null; + } } }); - } - - public void onTagIconClicked1(View view) { - getTagData(); -// tagListAdapter = new ExpandableListAdapterS(this, App.tagList, tagListView); -// tagListView.setAdapter(tagListAdapter); - tagListAdapter.notifyDataSetChanged(); - tagBottomSheetDialog.show(); - KLog.e("tag按钮被点击"); - } + // 创建菜单: + SwipeMenuCreator mSwipeMenuCreator = new SwipeMenuCreator() { + @Override + public void onCreateMenu(SwipeMenu leftMenu, SwipeMenu rightMenu, int position) { + Article article = articlesAdapter.getItem(position); +// Article article = CoreDB.i().articleDao().getById(App.i().getUser().getId(),articlesAdapter.getArticleId(position)); + if(article==null){ + //KLog.i("文章数据为null: " + position + " , " + articlesAdapter.getCurrentList().getLastKey() + " , " + articlesAdapter.getCurrentList().getLoadedCount() ); + return; + } +// else { +// KLog.i("文章数据为: " + position + " , " + articlesAdapter.getCurrentList().getLoadedCount() + " , " + articlesAdapter.getCurrentList().getLastKey() ); +// } - BottomSheetDialog tagBottomSheetDialog; - RelativeLayout relativeLayout; - public void initTagListView() { - tagBottomSheetDialog = new BottomSheetDialog(MainActivity.this); - tagBottomSheetDialog.setContentView(R.layout.main_bottom_sheet_tag); - View view = tagBottomSheetDialog.getWindow().findViewById(android.support.design.R.id.design_bottom_sheet); - BottomSheetBehavior.from(view).setPeekHeight(ScreenUtil.getScreenHeight(this)); + int width = getResources().getDimensionPixelSize(R.dimen.dp_80); + int margin = getResources().getDimensionPixelSize(R.dimen.dp_30); - relativeLayout = tagBottomSheetDialog.findViewById(R.id.sheet_tag); - IconFontView iconFontView = tagBottomSheetDialog.findViewById(R.id.main_tag_close); - iconFontView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - tagBottomSheetDialog.dismiss(); - } - }); - tagListView = tagBottomSheetDialog.findViewById(R.id.main_tag_list_view); +// KLog.e("添加左右菜单" + position ); + // 1. MATCH_PARENT 自适应高度,保持和Item一样高; 2. 指定具体的高,比如80; 3. WRAP_CONTENT,自身高度,不推荐; + int height = ViewGroup.LayoutParams.MATCH_PARENT; - // 设置悬浮头部VIEW - headerPinnedView = getLayoutInflater().inflate(R.layout.tag_expandable_item_group_header, tagListView, false); - tagListView.setPinnedHeaderView(headerPinnedView); - tagListView.setOnPinnedGroupClickListener(new ExpandableListViewS.OnPinnedGroupClickListener() { - @Override - public void onHeaderClick(ExpandableListView parent, View v, int pinnedGroupPosition) { - if (parent.isGroupExpanded(pinnedGroupPosition)) { - tagListView.collapseGroup(pinnedGroupPosition); + SwipeMenuItem starItem = new SwipeMenuItem(MainActivity.this); // 各种文字和图标属性设置。 + if (article.getStarStatus() == App.STATUS_STARED) { + starItem.setImage(R.drawable.ic_state_unstar); } else { - tagListView.expandGroup(pinnedGroupPosition); + starItem.setImage(R.drawable.ic_state_star); } + starItem.setWeight(width); + starItem.setHeight(height); + starItem.setMargins(margin, 0, margin, 0); + leftMenu.addMenuItem(starItem); // 在Item左侧添加一个菜单。 + + SwipeMenuItem readItem = new SwipeMenuItem(MainActivity.this); // 各种文字和图标属性设置。 + if (article.getReadStatus() == App.STATUS_READED) { + readItem.setImage(R.drawable.ic_state_unread); + } else { + readItem.setImage(R.drawable.ic_read); + } + readItem.setWeight(width); + readItem.setHeight(height); + readItem.setMargins(margin, 0, margin, 0); + rightMenu.addMenuItem(readItem); // 在Item右侧添加一个菜单。 + // 注意:哪边不想要菜单,那么不要添加即可。 } - }); - -// headerHomeView = getLayoutInflater().inflate(R.layout.tag_expandable_item_header_home, tagListView, false); -// tagListView.addHeaderView(headerHomeView); -// headerHomeView.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// KLog.e("点击了所有"); -// getArtDataAll(); -// } -// }); - + }; + articleListView.setSwipeMenuCreator(mSwipeMenuCreator); - tagListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { + articleListView.setOnItemSwipeListener(new OnItemSwipeListener() { @Override - public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { - tagBottomSheetDialog.dismiss(); - App.StreamId = App.tagList.get(groupPosition).getId().replace("\"", ""); - App.StreamTitle = App.tagList.get(groupPosition).getTitle(); -// if (App.StreamId == null || App.StreamId.equals("")) { -// App.StreamId = "user/" + App.UserID + "/state/com.google/reading-list"; -// } -// KLog.e("【 TagList 被点击】" + App.StreamId + App.StreamState); - WithPref.i().setStreamId(App.StreamId); - refreshData(); - return true; + public void onClose(View swipeMenu, int direction, int adapterPosition) { } - }); - tagListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { -// KLog.e("子项被点击1:" + v + " - " + v.getTag() + "-" + groupPosition + "==" + childPosition + "=" + id); - tagBottomSheetDialog.dismiss(); - Feed theFeed = App.tagList.get(groupPosition).getFeeds().get(childPosition); - - App.StreamId = theFeed.getId(); - App.StreamTitle = theFeed.getTitle(); - WithPref.i().setStreamId(App.StreamId); - refreshData(); - -// KLog.e("【子项被点击2】" + App.StreamId + App.StreamState); - return true; + public void onCloseLeft(int position) { + KLog.i("onCloseLeft:" + position + " "); + toggleStarState(position); } - }); - tagListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView parent, View view, final int position, long id) { -// KLog.e("被长安,view的id是" + view.getId() + ",parent的id" + parent.getId() + ",Tag是" + view.getTag() + ",位置是" + tagListView.getPositionForView(view)); - - ExpandableListAdapterS.ItemViewHolder itemView = (ExpandableListAdapterS.ItemViewHolder) view.getTag(); - if (InoApi.isTag(itemView.id) && position != 0 && position != 1) { - showTagDialog(WithDB.i().getTag(itemView.id)); - } else if (InoApi.isFeed(itemView.id)) { - showFeedDialog(itemView, WithDB.i().getFeed(itemView.id)); - } - return true; + public void onCloseRight(int position) { + KLog.i("onCloseRight:" + position + " "); + toggleReadState(position); } }); - } - private boolean autoMarkReaded = false; - private int lastAutoMarkPos = -1; + OnItemMenuClickListener mItemMenuClickListener = new OnItemMenuClickListener() { + @Override + public void onItemClick(SwipeMenuBridge menuBridge, int position) { + // 任何操作必须先关闭菜单,否则可能出现Item菜单打开状态错乱。 + menuBridge.closeMenu(); - private void showConfirmDialog(final int start, final int end) { - new AlertDialog.Builder(MainActivity.this) - .setMessage(R.string.main_dialog_confirm_mark_article_list) - .setPositiveButton("确认", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Integer[] index = new Integer[2]; - index[0] = start; - index[1] = end; - new MarkListReadedAsyncTask().execute(index); + // 左侧还是右侧菜单: + int direction = menuBridge.getDirection(); + + if (direction == SwipeRecyclerView.RIGHT_DIRECTION) { + KLog.i("onItemClick onCloseRight:" + position + " "); + if (position > -1) { + toggleReadState(position); } - }) - .setNegativeButton("取消", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); + } else if (direction == SwipeRecyclerView.LEFT_DIRECTION) { + KLog.i("onItemClick onCloseLeft:" + position + " "); + if (position > -1) { + toggleStarState(position); } - }) - .show(); - } + } + } + }; + // 菜单点击监听。 + articleListView.setOnItemMenuClickListener(mItemMenuClickListener); - public void initArtListView() { - articleListView = findViewById(R.id.main_slv); - if (articleListView == null) { - return; - } -// imageLoader = new ImageLoader(this,articleListView); - // 由于item内有view添加了onItemClickListener,所以事件被消耗,没有回调到ListView OnItemLongClick方法。 - articleListView.setOnListItemLongClickListener(new ListViewS.OnListItemLongClickListener() { + articleListView.setOnItemClickListener(new OnItemClickListener() { @Override - public void onListItemLongClick(View view, final int position) { -// KLog.e("长按==="); - final MaterialSimpleListAdapter adapter = new MaterialSimpleListAdapter(MainActivity.this); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content(R.string.main_slv_dialog_mark_up) - .icon(R.drawable.dialog_ic_mark_up) - .backgroundColor(Color.WHITE) - .build()); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content(R.string.main_slv_dialog_mark_down) - .icon(R.drawable.dialog_ic_mark_down) - .backgroundColor(Color.WHITE) - .build()); - adapter.add(new MaterialSimpleListItem.Builder(MainActivity.this) - .content(R.string.main_slv_dialog_mark_unread) - .icon(R.drawable.dialog_ic_mark_unread) - .backgroundColor(Color.WHITE) - .build()); - - new MaterialDialog.Builder(MainActivity.this) - .adapter(adapter, new MaterialDialog.ListCallback() { - @Override - public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { - Integer[] index = new Integer[2]; - switch (which) { - case 0: - index[0] = 0; - index[1] = position + 1; - MarkListReadedAsyncTask changeReadedList = new MarkListReadedAsyncTask(); - changeReadedList.execute(index); - break; - case 1: - showConfirmDialog(position, App.articleList.size()); - break; - case 2: - Article article = App.articleList.get(position); - if (article.getReadStatus() == Api.READED) { - DataApi.i().markArticleUnread(article.getId(), null); - } - // 方法2 - WithDB.i().setUnreading(article); + public void onItemClick(View view, int position) { + if (position < 0) { + return; + } + Intent intent = new Intent(MainActivity.this, ArticleActivity.class); + intent.putExtra("theme", App.i().getUser().getThemeMode()); - articleListAdapter.notifyDataSetChanged(); - break; - default: - break; - } + String articleId = articlesAdapter.getItem(position).getId(); + //String articleId = articlesAdapter.getArticleId(position); - dialog.dismiss(); - } - }) - .show(); + intent.putExtra("articleId", articleId); + // 下标从 0 开始 + intent.putExtra("articleNo", position); + intent.putExtra("articleCount", articlesAdapter.getItemCount()); + startActivityForResult(intent, 0); + overridePendingTransition(R.anim.in_from_bottom, R.anim.fade_out); + + //KLog.i("点击了" + articleID + ",位置:" + position + ",文章ID:" + articleID + " " + App.articleList.size()); } }); + articleViewModel = new ViewModelProvider(this).get(ArticleViewModel.class); + articlesAdapter = new ArticlePagedListAdapter(); + articleListView.setAdapter(articlesAdapter); + App.i().articlesAdapter = articlesAdapter; + } - articleListView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView absListView, int scrollState) { -// if( scrollState==SCROLL_STATE_IDLE ){ -// imageLoader.loadImages(firstVisibleItemPosition,lastVisibleItemPosition); -// } - } +// private CategoryViewModel categoryViewModel; +// public void onTagIconClicked(View view) { +// KLog.i("tag按钮被点击"); +// tagBottomSheetDialog.show(); +// if(categoryViewModel == null){ +// categoryViewModel = new ViewModelProvider(this).get(CategoryViewModel.class); +// } +// categoryViewModel.getCategoriesLiveData().observe(this, new Observer>() { +// @Override +// public void onChanged(List categories) { +// String userId = App.i().getUser().getUserId(); +// // 总分类 +// Category rootCategory = new Category(); +// rootCategory.setTitle(getString(R.string.all)); +// int unreadCount = CoreDB.i().articleDao().getUnreadCount(App.i().getUser().getId()); +// rootCategory.setUnreadCount(unreadCount); +// rootCategory.setId("user/" + userId + App.CATEGORY_ALL); +// +// // 未分类 +// Category unCategory = new Category(); +// unCategory.setTitle(getString(R.string.un_category)); +// int unreadUnCategoryCount = CoreDB.i().articleDao().getUnreadUncategoryCount(App.i().getUser().getId()); +// unCategory.setUnreadCount(unreadUnCategoryCount); +// unCategory.setId("user/" + userId + App.CATEGORY_UNCATEGORIZED); +// +// categories.add(0,rootCategory); +// categories.add(1,unCategory); +// tagListAdapter.setParents(categories); +// tagListAdapter.notifyDataChanged(); +//// CategoryDiffCallback callback = new CategoryDiffCallback(tagListAdapter.getCategories(), categories); +//// //对比数据 +//// DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback); +//// //然后刷新,完事儿 +//// result.dispatchUpdatesTo(tagListAdapter); +// } +// }); +// } + public void onClickCategoryIcon(View view) { + tagBottomSheetDialog.show(); + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override - public void onScroll(AbsListView absListView, final int firstVisibleItem, int visibleItemCount, int totalItemCount) { -// KLog.e("滚动:" + firstVisibleItem+ " " + visibleItemCount + " " + totalItemCount); - Integer[] index = new Integer[2]; - index[0] = firstVisibleItem; - new MarkReadedAsyncTask().execute(index); - -// firstVisibleItemPosition = firstVisibleItem; -// lastVisibleItemPosition = firstVisibleItem + visibleItemCount; -// // 当首次显示listview时加载图片 -// if( isFirstIn && visibleItemCount>0 ){ -// imageLoader.loadImages(firstVisibleItemPosition,lastVisibleItemPosition); -// isFirstIn = false; -// } -// // 取消滚出屏幕的下载任务 -// imageLoader.cancelLoadTask(firstVisibleItem-1); + public void run() { + String uid = App.i().getUser().getUserId(); + + // 总分类 + Collection rootCategory = new Collection(); + rootCategory.setTitle(getString(R.string.all)); + int unreadCount = CoreDB.i().articleDao().getUnreadCount(App.i().getUser().getId()); + rootCategory.setId("user/" + uid + App.CATEGORY_ALL); + + // 未分类 + Collection unCategory = new Collection(); + unCategory.setTitle(getString(R.string.un_category)); + int unreadUnCategoryCount = CoreDB.i().articleDao().getUncategoryUnreadCount(App.i().getUser().getId()); + unCategory.setId("user/" + uid + App.CATEGORY_UNCATEGORIZED); + + // 已分类 + List categories; + + if( App.i().getUser().getStreamStatus() == App.STATUS_UNREAD ){ + rootCategory.setCount(CoreDB.i().articleDao().getUnreadCount(App.i().getUser().getId())); + unCategory.setCount(CoreDB.i().articleDao().getUncategoryUnreadCount(App.i().getUser().getId())); + categories = CoreDB.i().categoryDao().getCategoriesUnreadCount(App.i().getUser().getId()); + }else if( App.i().getUser().getStreamStatus() == App.STATUS_STARED ){ + rootCategory.setCount(CoreDB.i().articleDao().getStarCount(App.i().getUser().getId())); + unCategory.setCount(CoreDB.i().articleDao().getUncategoryStarCount(App.i().getUser().getId())); + categories = CoreDB.i().categoryDao().getCategoriesStarCount(App.i().getUser().getId()); + }else { + rootCategory.setCount(CoreDB.i().articleDao().getAllCount(App.i().getUser().getId())); + unCategory.setCount(CoreDB.i().articleDao().getUncategoryAllCount(App.i().getUser().getId())); + categories = CoreDB.i().categoryDao().getCategoriesAllCount(App.i().getUser().getId()); + } + + List categoryListTemp = new ArrayList<>(); + categoryListTemp.add(rootCategory); + categoryListTemp.add(unCategory); + // 数据库中的所有分类 + categoryListTemp.addAll(categories); + runOnUiThread(new Runnable() { + @Override + public void run() { + tagListAdapter.setParents(categoryListTemp); + tagListAdapter.notifyDataChanged(); + KLog.i("tag按钮被点击"); + } + }); } }); + } - articleListView.setItemSlideListener(new ListViewS.OnItemSlideListener() { - @Override - public void onUpdate(View view, int position, float offset) { - // 推测由于该函数 getView 已经生成了 View 所以不在更新了。使用 notifyDataSetChanged(); 也不行 -// KLog.e("观察" + offset + " " + lastOffset); -// SearchListViewAdapter.CustomViewHolder itemViewHolder; -// int firstVisiblePosition = articleListView.getFirstVisiblePosition(); //屏幕内当前可以看见的第一条数据 -// if( position-firstVisiblePosition>=0){ -// View itemView = articleListView.getChildAt(position - firstVisiblePosition); -// itemViewHolder = (SearchListViewAdapter.CustomViewHolder) itemView.getTag(); +// public void onClickCategoryIcon2(View view) { +// tagBottomSheetDialog.show(); +// AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { +// @Override +// public void run() { +// String uid = App.i().getUser().getUserId(); +// +// // 总分类 +// Category rootCategory = new Category(); +// rootCategory.setTitle(getString(R.string.all)); +// int unreadCount = CoreDB.i().articleDao().getUnreadCount(App.i().getUser().getId()); +// rootCategory.setUnreadCount(unreadCount); +// rootCategory.setId("user/" + uid + App.CATEGORY_ALL); // -// if( offset < -0.6 && hadChanged==false ){ -// if( (offset - lastOffset) > 0 ){ // 向右滑 -// itemViewHolder.markLeft.setTextColor( getResources().getColor(R.color.crimson)); -// hadChanged=true; -// }else { -// itemViewHolder.markLeft.setTextColor( getResources().getColor(R.color.colorPrimary)); -// hadChanged=false; -// } -// KLog.e("变色" ); +// // 未分类 +// Category unCategory = new Category(); +// unCategory.setTitle(getString(R.string.un_category)); +// int unreadUnCategoryCount = CoreDB.i().articleDao().getUnreadUncategoryCount(App.i().getUser().getId()); +// unCategory.setUnreadCount(unreadUnCategoryCount); +// unCategory.setId("user/" + uid + App.CATEGORY_UNCATEGORIZED); // -// }else if( offset > 0.6 && hadChanged==false ){ -// KLog.e("变色" ); -// if( ( offset - lastOffset ) > 0 ){ // 向左滑 -// itemViewHolder.markLeft.setTextColor( getResources().getColor(R.color.crimson)); -// hadChanged=true; -// }else { -// itemViewHolder.markLeft.setTextColor( getResources().getColor(R.color.colorPrimary)); -// hadChanged=false; -// } +// List categoryListTemp = new ArrayList<>(); +// categoryListTemp.add(rootCategory); +// categoryListTemp.add(unCategory); +//// categoryListTemp.add(tagCategory); +// +// List categories; +// categories = CoreDB.i().categoryDao().getAll(App.i().getUser().getId()); +// +// // 数据库中的所有分类 +// categoryListTemp.addAll(categories); +// runOnUiThread(new Runnable() { +// @Override +// public void run() { +// tagListAdapter.setParents(categoryListTemp); +// tagListAdapter.notifyDataChanged(); +// KLog.i("tag按钮被点击"); // } -// } -// lastOffset = offset; - } - - @Override - public void onCloseLeft(View view, int position, int direction) { - KLog.e("onCloseLeft:" + position + " "); - if (position > -1) { - Article article = App.articleList.get(position); - toggleStarState(article); - } - } +// }); +// } +// }); +// } - @Override - public void onCloseRight(View view, int position, int direction) { - KLog.e("onCloseRight:" + position + " "); - if (position > -1) { - Article article = App.articleList.get(position); - toggleReadState(article); - } + public void initTagListView() { + tagBottomSheetDialog = new BottomSheetDialog(MainActivity.this); + tagBottomSheetDialog.setContentView(R.layout.bottom_sheet_category); + relativeLayout = tagBottomSheetDialog.findViewById(R.id.sheet_tag); - } + IconFontView iconFontView = tagBottomSheetDialog.findViewById(R.id.main_tag_close); + iconFontView.setOnClickListener(view -> { + tagBottomSheetDialog.dismiss(); + // iconFontView.setVisibility(View.GONE); + }); - // 由于父 listview 被重载,onClick 事件也被重写了。无法直接使用 setOnItemClickListener - @Override - public void onClick(View view, int position) { - if (position == -1) { - return; - } + tagListView = tagBottomSheetDialog.findViewById(R.id.main_tag_list_view); - Intent intent = new Intent(MainActivity.this, ArticleActivity.class); + tagListView.setLayoutManager(new LinearLayoutManager(this)); + // 还有另外一种方案,通过设置动画执行时间为0来解决问题: + tagListView.getItemAnimator().setChangeDuration(0); + // 关闭默认的动画 + ((SimpleItemAnimator) tagListView.getItemAnimator()).setSupportsChangeAnimations(false); -// String[] articleIDs = new String[App.articleList.size()]; -// for (int i=0, size = App.articleList.size(); i(Arrays.asList(articleIDs)) ); + //stickyHeaderLayout = tagBottomSheetDialog.findViewById(R.id.sticky_header_layout); + //headerPinnedView = getLayoutInflater().inflate(R.layout.tag_expandable_item_group_header, stickyHeaderLayout, false); + //stickyHeaderLayout.setStickyHeaderView(headerPinnedView); - String articleID = App.articleList.get(position).getId(); - intent.putExtra("articleId", articleID); - // 下标从 0 开始 - intent.putExtra("articleNo", position); - intent.putExtra("articleCount", App.articleList.size()); - startActivityForResult(intent, 0); - overridePendingTransition(R.anim.in_from_bottom, R.anim.fade_out); -// KLog.i("点击了" + articleID + ",位置:" + position + ",文章ID:" + articleID + " " + App.articleList.size()); + tagListAdapter = new ExpandedAdapter(this); + tagListView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(View view, int adapterPosition) { + tagBottomSheetDialog.dismiss(); + // 根据原position判断该item是否是parent item + int groupPosition = tagListAdapter.parentItemPosition(adapterPosition); + User user = App.i().getUser(); + if (tagListAdapter.isParentItem(adapterPosition)) { + App.i().getUser().setStreamId( tagListAdapter.getGroup(groupPosition).getId().replace("\"", "") ); + App.i().getUser().setStreamTitle( tagListAdapter.getGroup(groupPosition).getTitle() ); + App.i().getUser().setStreamType( App.TYPE_GROUP ); + //KLog.i("【 TagList 被点击】" + App.i().getUser().toString()); + refreshData(); + } else { + int childPosition = tagListAdapter.childItemPosition(adapterPosition); + Collection feed = tagListAdapter.getChild(groupPosition, childPosition); + App.i().getUser().setStreamId( feed.getId() ); + App.i().getUser().setStreamTitle( feed.getTitle() ); + App.i().getUser().setStreamType( App.TYPE_FEED ); + refreshData(); + } + CoreDB.i().userDao().update(user); } + }); + tagListView.setOnItemLongClickListener(new OnItemLongClickListener() { @Override - public void log(String layout) { - KLog.e(layout); + public void onItemLongClick(View view, int adapterPosition) { + // KLog.e("被长安,view的id是" + allArticleHeaderView.getId() + ",parent的id" + parent.getId() + ",Tag是" + allArticleHeaderView.getCategoryById() + ",位置是" + tagListView.getPositionForView(allArticleHeaderView)); + // 根据原position判断该item是否是parent item + if (tagListAdapter.isParentItem(adapterPosition)) { + int parentPosition = tagListAdapter.parentItemPosition(adapterPosition); + showTagDialog(tagListAdapter.getGroup(parentPosition)); + } else { + // 换取child position + int parentPosition = tagListAdapter.parentItemPosition(adapterPosition); + int childPosition = tagListAdapter.childItemPosition(adapterPosition); + showFeedActivity(parentPosition, childPosition); + } } }); + tagListView.setAdapter(tagListAdapter); } + private void showConfirmDialog(final int start, final int end) { + new AlertDialog.Builder(MainActivity.this) + .setMessage(R.string.main_dialog_confirm_mark_article_list) + .setPositiveButton(R.string.confirm, (dialog, which) -> { + Integer[] index = new Integer[2]; + index[0] = start; + index[1] = end; + new MarkListReadedAsyncTask().execute(index); + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .show(); + } - // Params, Progress, Result - private class MarkReadedAsyncTask extends AsyncTask { - @Override - protected Integer doInBackground(Integer... params) { - int firstVisibleItemPos = params[0]; - if (autoMarkReaded - && lastAutoMarkPos != firstVisibleItemPos - && App.articleList.get(firstVisibleItemPos).getReadStatus() == Api.UNREAD) { + // 标记以上/以下为已读 + @SuppressLint("StaticFieldLeak") + private class MarkListReadedAsyncTask extends AsyncTask { + private List
articleList; + private List articleIDs; + private void handleArticle(int i){ + try { + int retry = 0; + Article article = articlesAdapter.getItem(i); + if( article == null ){ + articlesAdapter.load(i); + do { + //KLog.i("文章为空:" + i ); + Thread.sleep(500); + retry ++; + article = articlesAdapter.getItem(i); + }while ( article == null && retry < 3 ); + //KLog.e("文章是否为空:" + (article==null) + " , " + (articlesAdapter.getItem(i)==null) ); + if( article == null ){ return; } + } - DataApi.i().markArticleReaded(App.articleList.get(firstVisibleItemPos).getId(), null); - // 方法2 - WithDB.i().setReaded(App.articleList.get(firstVisibleItemPos)); - lastAutoMarkPos = firstVisibleItemPos; -// KLog.e("标记已读:" + lastAutoMarkPos); + articlesAdapter.setLastItem(linearLayoutManager.findLastVisibleItemPosition()-1); + if (article.getReadStatus() == App.STATUS_UNREAD) { + article.setReadStatus(App.STATUS_READED); + articleList.add(article); + articleIDs.add(article.getId()); + //提交之后,会执行onProcessUpdate方法,通知对应这个item更新界面 + publishProgress(i); + } + } catch (IllegalStateException | InterruptedException e) { + KLog.e("获取数据错误"); + e.printStackTrace(); } - - //返回结果 - return 0; } - } - - - // Params, Progress, Result - private class MarkListReadedAsyncTask extends AsyncTask { @Override protected Integer doInBackground(Integer... params) { int startIndex, endIndex; + boolean desc; + desc = params[0] >= params[1]; startIndex = params[0]; endIndex = params[1]; - List
articleList = new ArrayList<>(endIndex - startIndex); - List articleIDs = new ArrayList<>(endIndex - startIndex); - List feedIDs = new ArrayList<>(endIndex - startIndex); - ArrayMap feedIDMap = new ArrayMap<>(endIndex - startIndex); - -// for (int i = endIndex - 1; i >= startIndex; i--) { - Article article; - for (int i = startIndex; i < endIndex; i++) { - article = App.articleList.get(i); - if (article.getReadStatus() == Api.UNREAD) { - article.setReadStatus(Api.READED); - articleList.add(article); - articleIDs.add(article.getId()); - feedIDs.add(article.getOriginStreamId()); - if (feedIDMap.containsKey(article.getOriginStreamId())) { - feedIDMap.put(article.getOriginStreamId(), feedIDMap.get(article.getOriginStreamId()) + 1); - } else { - feedIDMap.put(article.getOriginStreamId(), 1); - } -// WithDB.i().setReaded(App.articleList.get(i)); -// DataApi.i().changeUnreadCount(App.articleList.get(i).getOriginStreamId(), -1); + if( desc ){ + articleList = new ArrayList<>(startIndex - endIndex); + articleIDs = new ArrayList<>(startIndex - endIndex); + for (int i = startIndex - 1; i >= endIndex; i--){ + handleArticle(i); + } + }else { + articleList = new ArrayList<>(endIndex - startIndex); + articleIDs = new ArrayList<>(endIndex - startIndex); + for (int i = startIndex; i < endIndex; i++){ + handleArticle(i); } } if (articleIDs.size() == 0) { - onCancelled(); return 0; } - DataApi.i().markArticleListReaded(articleIDs, null); - WithDB.i().saveArticles(articleList); + if (desc) { + Collections.reverse(articleList); + Collections.reverse(articleIDs); + } + int needCount = articleIDs.size(); + int hadCount = 0; + int num = 0; + + while (needCount > 0) { + num = Math.min(100, needCount); + List
subArticles = articleList.subList(hadCount, hadCount + num); + List subArticleIDs = articleIDs.subList(hadCount, hadCount + num); + hadCount = hadCount + num; + CoreDB.i().articleDao().update(subArticles); + App.i().getApi().markArticleListReaded(subArticleIDs, new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + for (Article article: subArticles) { + article.setReadStatus(App.STATUS_UNREAD); + } + CoreDB.i().articleDao().update(subArticles); + } + }); - List feeds = WithDB.i().getFeeds(feedIDs); - for (Feed feed : feeds) { - feed.setUnreadCount(feed.getUnreadCount() - feedIDMap.get(feed.getId())); + needCount = articleIDs.size() - hadCount; } - WithDB.i().saveFeeds(feeds); - //提交之后,会执行onProcessUpdate方法 - publishProgress(-articleIDs.size()); //返回结果 return 0; } @@ -1050,54 +1001,122 @@ protected Integer doInBackground(Integer... params) { */ @Override protected void onProgressUpdate(Integer... progress) { - articleListAdapter.notifyDataSetChanged(); + KLog.e("更新进度" + progress[0] ); // 应该是去通知对应的那个 item 改变。 + articlesAdapter.notifyItemChanged(progress[0]); } } private void showSearchResult(String keyword) { -// List
articles = WithDB.i().getSearchedArts( keyword ); -// App.i().updateArtList(WithDB.i().getSearchedArts(keyword)); - App.StreamId = Api.U_Search; - App.StreamTitle = getString(R.string.main_toolbar_title_search) + keyword; - App.articleList = WithDB.i().getSearchedArts(keyword); - articleListAdapter = new MainListViewAdapter(this, App.articleList, articleListView); - articleListView.setAdapter(articleListAdapter); - loadViewByData(); + User user = App.i().getUser(); + user.setStreamId(App.CATEGORY_SEARCH); + user.setStreamTitle(getString(R.string.main_toolbar_title_search) + keyword); + CoreDB.i().userDao().update(user); + + if(articleViewModel.articles != null && articleViewModel.articles.hasObservers()){ + articleViewModel.articles.removeObservers(this); + articleViewModel.articles = null; + } + articleViewModel.getAllByKeyword(App.i().getUser().getId(),keyword).observe(this, new Observer>() { + @Override + public void onChanged(PagedList
articles) { + articlesAdapter.submitList(articles); + loadViewByData( articles.size() ); + } + }); + articleListView.scrollToPosition(0); } + private void toggleReadState(final int position) { + if (position < 0) { + return; + } + // String articleId = articlesAdapter.getItem(position).getId(); + // Article article = CoreDB.i().articleDao().getById(App.i().getUser().getId(),articleId); + Article article = articlesAdapter.getItem(position); + if (autoMarkReaded && article.getReadStatus() == App.STATUS_UNREAD) { + article.setReadStatus(App.STATUS_UNREADING); + CoreDB.i().articleDao().update(article); + } else if (article.getReadStatus() == App.STATUS_READED) { + article.setReadStatus(App.STATUS_UNREADING); + CoreDB.i().articleDao().update(article); + App.i().getApi().markArticleUnread(article.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + article.setReadStatus(App.STATUS_READED); + CoreDB.i().articleDao().update(article); + KLog.e("失败的原因是:" + error ); + } + }); + } else { + article.setReadStatus(App.STATUS_READED); + CoreDB.i().articleDao().update(article); + App.i().getApi().markArticleReaded(article.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } - private void toggleReadState(final Article article) { - if (autoMarkReaded && article.getReadStatus() == Api.UNREAD) { - WithDB.i().setUnreading(article); - } else if (article.getReadStatus() == Api.READED) { - DataApi.i().markArticleUnread(article.getId(), null); - WithDB.i().setUnreading(article); - }else { - DataApi.i().markArticleReaded(article.getId(), null); - WithDB.i().setReaded(article); + @Override + public void onFailure(Object error) { + article.setReadStatus(App.STATUS_UNREAD); + CoreDB.i().articleDao().update(article); + } + }); } - articleListAdapter.notifyDataSetChanged(); +// KLog.e("修改状态:" + position + " " + article); + articlesAdapter.notifyItemChanged(position); } - private void toggleStarState(final Article article) { - if (article.getStarStatus() == Api.STARED) { - article.setStarStatus(Api.UNSTAR); - DataApi.i().markArticleUnstar(article.getId(), null); - }else { - article.setStarStatus(Api.STARED); - DataApi.i().markArticleStared(article.getId(), null); - article.setStarred(System.currentTimeMillis() / 1000); + private void toggleStarState(final int position) { + if (position < 0) { + return; } - WithDB.i().saveArticle(article); - articleListAdapter.notifyDataSetChanged(); - } + Article article = articlesAdapter.getItem(position); +// String articleId = articlesAdapter.getItem(position).getId(); +// Article article = CoreDB.i().articleDao().getById(App.i().getUser().getId(),articleId); + + if (article.getStarStatus() == App.STATUS_STARED) { + article.setStarStatus(App.STATUS_UNSTAR); + CoreDB.i().articleDao().update(article); + + App.i().getApi().markArticleUnstar(article.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + @Override + public void onFailure(Object error) { + article.setStarStatus(App.STATUS_STARED); + CoreDB.i().articleDao().update(article); + } + }); + + } else { + article.setStarStatus(App.STATUS_STARED); + CoreDB.i().articleDao().update(article); + App.i().getApi().markArticleStared(article.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + article.setStarStatus(App.STATUS_UNSTAR); + CoreDB.i().articleDao().update(article); + } + }); + } + articlesAdapter.notifyItemChanged(position); + } - // TODO: 2018/3/4 改用观察者模式。http://iaspen.cn/2015/05/09/观察者模式在android%20上的最佳实践 + // TODO: 2018/3/4 改用观察者模式。http://iaspen.cn/2015/05/09/观察者模式在android上的最佳实践 /** * 在android中从A页面跳转到B页面,然后B页面进行某些操作后需要通知A页面去刷新数据, * 我们可以通过startActivityForResult来唤起B页面,然后再B页面结束后在A页面重写onActivityResult来接收返回结果从而来刷新页面。 @@ -1109,24 +1128,25 @@ private void toggleStarState(final Article article) { * 因此考虑使用观察者模式去处理这个问题。 */ @Override - protected void onActivityResult(int requestCode , int resultCode , Intent intent){ + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); // KLog.e("------------------------------------------" + resultCode + requestCode); - switch (resultCode){ - // 这一段应该用不到了 - case Api.ActivityResult_TagToMain: - refreshData(); // TagToMain - break; - case Api.ActivityResult_ArtToMain: + switch (resultCode) { + case App.ActivityResult_ArtToMain: // 在文章页的时候读到了第几篇文章,好让列表也自动将该项置顶 int articleNo = intent.getExtras().getInt("articleNo"); - if (articleNo > articleListView.getLastVisiblePosition()) { + +// LinearLayoutManager linearLayoutManager = (LinearLayoutManager) articleListView.getLayoutManager(); + assert linearLayoutManager != null; + if (articleNo > linearLayoutManager.findLastVisibleItemPosition() - 1) { slvSetSelection(articleNo); } -// KLog.e("【onActivityResult】" + articleNo + " " + articleListView.getLastVisiblePosition()); + articlesAdapter.notifyDataSetChanged(); break; - case Api.ActivityResult_SearchLocalArtsToMain: + case App.ActivityResult_SearchLocalArtsToMain: // KLog.e("被搜索的词是" + intent.getExtras().getString("searchWord")); showSearchResult(intent.getExtras().getString("searchWord")); + articlesAdapter.notifyDataSetChanged(); break; default: break; @@ -1135,19 +1155,21 @@ protected void onActivityResult(int requestCode , int resultCode , Intent intent // 滚动到指定位置 private void slvSetSelection(final int position) { - articleListView.post(new Runnable() { + // 保证滚动到指定位置时,view至最顶端 + LinearSmoothScroller smoothScroller = new LinearSmoothScroller(this) { @Override - public void run() { - articleListView.setSelection(position); // 不能直接用这个,无法滚动 + protected int getVerticalSnapPreference() { + return LinearSmoothScroller.SNAP_TO_START; } - }); + }; + smoothScroller.setTargetPosition(position); + Objects.requireNonNull(articleListView.getLayoutManager()).startSmoothScroll(smoothScroller); } public void clickRefreshIcon(View view) { refreshData(); } - private BottomSheetDialog quickSettingDialog; public void onQuickSettingIconClicked(View view) { quickSettingDialog = new BottomSheetDialog(MainActivity.this); quickSettingDialog.setContentView(R.layout.main_bottom_sheet_more); @@ -1156,19 +1178,6 @@ public void onQuickSettingIconClicked(View view) { // quickSettingDialog.setCancelable(false); // dialog无法取消,按返回键都取消不了 View moreSetting = quickSettingDialog.findViewById(R.id.more_setting); - SwitchButton autoMarkWhenScrolling = quickSettingDialog.findViewById(R.id.auto_mark_when_scrolling_switch); - SwitchButton downImgOnWifiSwitch = quickSettingDialog.findViewById(R.id.down_img_on_wifi_switch); - RadioGroup radioGroup = quickSettingDialog.findViewById(R.id.article_list_state_radio_group); - final RadioButton radioAll = quickSettingDialog.findViewById(R.id.radio_all); - final RadioButton radioUnread = quickSettingDialog.findViewById(R.id.radio_unread); - final RadioButton radioStarred = quickSettingDialog.findViewById(R.id.radio_starred); - SwitchButton nightThemeWifiSwitch = quickSettingDialog.findViewById(R.id.night_theme_switch); - - autoMarkWhenScrolling.setChecked(WithPref.i().isScrollMark()); - downImgOnWifiSwitch.setChecked(WithPref.i().isDownImgOnlyWifi()); - nightThemeWifiSwitch.setChecked(WithPref.i().getThemeMode() == App.Theme_Night); - - quickSettingDialog.show(); moreSetting.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -1176,90 +1185,104 @@ public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SettingActivity.class); startActivity(intent); overridePendingTransition(R.anim.in_from_bottom, R.anim.fade_out); -// startActivityForResult(intent, 0); } }); - autoMarkWhenScrolling.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - KLog.e("onClickedAutoMarkWhenScrolling图标被点击"); - WithPref.i().setScrollMark(b); - autoMarkReaded = b; + + SwitchButton autoMarkWhenScrolling = quickSettingDialog.findViewById(R.id.auto_mark_when_scrolling_switch); + autoMarkWhenScrolling.setChecked(App.i().getUser().isMarkReadOnScroll()); + autoMarkWhenScrolling.setOnCheckedChangeListener((compoundButton, b) -> { + KLog.e("onClickedAutoMarkWhenScrolling图标被点击"); + User user = App.i().getUser(); + user.setMarkReadOnScroll(b); + CoreDB.i().userDao().update(user); + autoMarkReaded = b; + if (autoMarkReaded) { + vToolbarAutoMark.setVisibility(View.VISIBLE); + } else { + vToolbarAutoMark.setVisibility(View.GONE); } }); + + SwitchButton downImgOnWifiSwitch = quickSettingDialog.findViewById(R.id.down_img_on_wifi_switch); + downImgOnWifiSwitch.setChecked(App.i().getUser().isDownloadImgOnlyWifi()); downImgOnWifiSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - WithPref.i().setDownImgWifi(b); + User user = App.i().getUser(); + user.setDownloadImgOnlyWifi(b); + CoreDB.i().userDao().update(user); } }); + + SwitchButton nightThemeWifiSwitch = quickSettingDialog.findViewById(R.id.night_theme_switch); + nightThemeWifiSwitch.setChecked(App.i().getUser().getThemeMode() == App.THEME_NIGHT); nightThemeWifiSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { quickSettingDialog.dismiss(); manualToggleTheme(); -// Tool.setWebViewsBGColor(); } }); -// if (App.StreamState.equals(Api.ART_STARED)) { -// radioStarred.setChecked(true); -// } else if (App.StreamState.equals(Api.ART_UNREAD)) { -// radioUnread.setChecked(true); - if (App.StreamStatus == Api.STARED) { + RadioGroup radioGroup = quickSettingDialog.findViewById(R.id.article_list_state_radio_group); + final RadioButton radioAll = quickSettingDialog.findViewById(R.id.radio_all); + final RadioButton radioUnread = quickSettingDialog.findViewById(R.id.radio_unread); + final RadioButton radioStarred = quickSettingDialog.findViewById(R.id.radio_starred); + if (App.i().getUser().getStreamStatus() == App.STATUS_STARED) { radioStarred.setChecked(true); - } else if (App.StreamStatus == Api.UNREAD) { + } else if (App.i().getUser().getStreamStatus() == App.STATUS_UNREAD) { radioUnread.setChecked(true); } else { radioAll.setChecked(true); } - radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { + User user = App.i().getUser(); if (i == radioStarred.getId()) { -// App.StreamState = Api.ART_STARED; - App.StreamStatus = Api.STARED; - toolbar.setNavigationIcon(R.drawable.state_star); + user.setStreamStatus(App.STATUS_STARED); + toolbar.setNavigationIcon(R.drawable.ic_state_star); } else if (i == radioUnread.getId()) { -// App.StreamState = Api.ART_UNREAD; - App.StreamStatus = Api.UNREAD; - toolbar.setNavigationIcon(R.drawable.state_unread); + user.setStreamStatus(App.STATUS_UNREAD); + toolbar.setNavigationIcon(R.drawable.ic_state_unread); } else { -// App.StreamState = Api.ART_ALL; - App.StreamStatus = Api.ALL; - toolbar.setNavigationIcon(R.drawable.state_all); + user.setStreamStatus(App.STATUS_ALL); + toolbar.setNavigationIcon(R.drawable.ic_state_all); } - WithPref.i().setStreamStatus(App.StreamStatus); + CoreDB.i().userDao().update(user); refreshData(); quickSettingDialog.dismiss(); } }); + + IconFontView iconFontView = quickSettingDialog.findViewById(R.id.main_more_close); + iconFontView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + quickSettingDialog.dismiss(); + } + }); + + quickSettingDialog.show(); } @OnClick(R.id.main_toolbar) public void clickToolbar(View view) { -// if(BuildConfig.DEBUG){ -// Intent intent = new Intent(this,TestActivity.class); -// startActivity(intent); -// return; -// } - if (maHandler.hasMessages(Api.MSG_DOUBLE_TAP)) { - maHandler.removeMessages(Api.MSG_DOUBLE_TAP); + if (maHandler.hasMessages(App.MSG_DOUBLE_TAP)) { + maHandler.removeMessages(App.MSG_DOUBLE_TAP); articleListView.smoothScrollToPosition(0); } else { - maHandler.sendEmptyMessageDelayed(Api.MSG_DOUBLE_TAP, ViewConfiguration.getDoubleTapTimeout()); + maHandler.sendEmptyMessageDelayed(App.MSG_DOUBLE_TAP, ViewConfiguration.getDoubleTapTimeout()); } } - /** * 监听返回键,弹出提示退出对话框 */ @Override - public boolean onKeyDown(int keyCode , KeyEvent event) { + public boolean onKeyDown(int keyCode, KeyEvent event) { // 后者为短期内按下的次数 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { quitDialog(); @@ -1287,6 +1310,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } + private RelativeLayout bottomBar; private void initToolbar() { toolbar = findViewById(R.id.main_toolbar); setSupportActionBar(toolbar); @@ -1296,19 +1320,19 @@ private void initToolbar() { getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(true); -// if (App.StreamState.equals(Api.ART_ALL)) { -// toolbar.setNavigationIcon(R.drawable.state_all); -// } else if (App.StreamState.equals(Api.ART_STARED)) { - if (App.StreamStatus == Api.ALL) { - toolbar.setNavigationIcon(R.drawable.state_all); - } else if (App.StreamStatus == Api.STARED) { - toolbar.setNavigationIcon(R.drawable.state_star); + if (App.i().getUser().getStreamStatus() == App.STATUS_ALL) { + toolbar.setNavigationIcon(R.drawable.ic_state_all); + } else if (App.i().getUser().getStreamStatus() == App.STATUS_STARED) { + toolbar.setNavigationIcon(R.drawable.ic_state_star); } else { - toolbar.setNavigationIcon(R.drawable.state_unread); + toolbar.setNavigationIcon(R.drawable.ic_state_unread); } + // 左上角图标是否显示,false则没有程序图标,仅标题。否则显示应用程序图标,对应id为android.R.id.home,对应ActionBar.DISPLAY_SHOW_HOME + // setDisplayShowHomeEnabled(true) + // 使自定义的普通View能在title栏显示,即actionBar.setCustomView能起作用,对应ActionBar.DISPLAY_SHOW_CUSTOM + // setDisplayShowCustomEnabled(true) - // setDisplayShowHomeEnabled(true) //使左上角图标是否显示,如果设成false,则没有程序图标,仅仅就个标题,否则,显示应用程序图标,对应id为android.R.id.home,对应ActionBar.DISPLAY_SHOW_HOME - // setDisplayShowCustomEnabled(true) // 使自定义的普通View能在title栏显示,即actionBar.setCustomView能起作用,对应ActionBar.DISPLAY_SHOW_CUSTOM + bottomBar = findViewById(R.id.main_bottombar); } @@ -1317,80 +1341,71 @@ private void initToolbar() { */ @Override protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + ViewGroupSetter articlesHeaderVS = new ViewGroupSetter((ViewGroup) articlesHeaderView); + articlesHeaderVS.childViewBgColor(R.id.main_header, R.attr.root_view_bg); + articlesHeaderVS.childViewTextColor(R.id.main_header_title, R.attr.lv_item_desc_color); + // articlesHeaderVS.childViewBgDrawable(R.id.main_header_eye, R.attr.lv_item_desc_color); + ViewGroupSetter artListViewSetter = new ViewGroupSetter(articleListView); // 绑定ListView的Item View中的news_title视图,在换肤时修改它的text_color属性 + // artListViewSetter.childViewBgColor(R.id.main_slv_item, R.attr.root_view_bg); artListViewSetter.childViewTextColor(R.id.main_slv_item_title, R.attr.lv_item_title_color); artListViewSetter.childViewTextColor(R.id.main_slv_item_summary, R.attr.lv_item_desc_color); artListViewSetter.childViewTextColor(R.id.main_slv_item_author, R.attr.lv_item_info_color); artListViewSetter.childViewTextColor(R.id.main_slv_item_time, R.attr.lv_item_info_color); artListViewSetter.childViewBgColor(R.id.main_slv_item_divider, R.attr.lv_item_divider); - artListViewSetter.childViewBgColor(R.id.main_slv_item, R.attr.root_view_bg); artListViewSetter.childViewBgColor(R.id.main_list_item_surface, R.attr.root_view_bg); - artListViewSetter.childViewBgColor(R.id.main_list_item_menu_left, R.attr.root_view_bg); - artListViewSetter.childViewBgColor(R.id.main_list_item_menu_right, R.attr.root_view_bg); - artListViewSetter.childViewBgColor(R.id.swipe_layout, R.attr.root_view_bg); - - ViewGroupSetter relative = new ViewGroupSetter(relativeLayout); - relative.childViewBgColor(R.id.main_tag_close, R.attr.bottombar_bg); - relative.childViewTextColor(R.id.main_tag_close, R.attr.bottombar_fg); - relative.childViewBgColor(R.id.sheet_tag, R.attr.bottombar_bg); - relative.childViewBgColor(R.id.main_tag_list_view, R.attr.bottombar_bg); + // artListViewSetter.childViewBgColor(R.id.main_list_item_menu_left, R.attr.root_view_bg); + // artListViewSetter.childViewBgColor(R.id.main_list_item_menu_right, R.attr.root_view_bg); + // artListViewSetter.childViewBgColor(R.id.swipe_layout, R.attr.root_view_bg); // 绑定ListView的Item View中的news_title视图,在换肤时修改它的text_color属性 ViewGroupSetter tagListViewSetter = new ViewGroupSetter(tagListView); - tagListViewSetter.childViewBgColor(R.id.group_item, R.attr.bottombar_bg); // 这个不生效,反而会影响底色修改 + tagListViewSetter.childViewBgColor(R.id.group_item, R.attr.root_view_bg); // 这个不生效,反而会影响底色修改 tagListViewSetter.childViewTextColor(R.id.group_item_icon, R.attr.tag_slv_item_icon); tagListViewSetter.childViewTextColor(R.id.group_item_title, R.attr.lv_item_title_color); tagListViewSetter.childViewTextColor(R.id.group_item_count, R.attr.lv_item_desc_color); - tagListViewSetter.childViewBgDrawable(R.id.group_item_count, R.attr.bubble_bg); + // tagListViewSetter.childViewBgDrawable(R.id.group_item_count, R.attr.bubble_bg); + + ViewGroupSetter relative = new ViewGroupSetter(relativeLayout); + relative.childViewBgColor(R.id.main_tag_close, R.attr.root_view_bg); + relative.childViewTextColor(R.id.main_tag_close, R.attr.bottombar_fg); + relative.childViewBgColor(R.id.sheet_tag, R.attr.root_view_bg); + relative.childViewBgColor(R.id.main_tag_list_view, R.attr.root_view_bg); + - tagListViewSetter.childViewBgColor(R.id.child_item, R.attr.bottombar_bg); // 这个不生效,反而会影响底色修改 + tagListViewSetter.childViewBgColor(R.id.child_item, R.attr.root_view_bg); // 这个不生效,反而会影响底色修改 tagListViewSetter.childViewTextColor(R.id.child_item_title, R.attr.lv_item_title_color); tagListViewSetter.childViewTextColor(R.id.child_item_count, R.attr.lv_item_desc_color); - tagListViewSetter.childViewBgDrawable(R.id.child_item_count, R.attr.bubble_bg); +// tagListViewSetter.childViewBgDrawable(R.id.child_item_count, R.attr.bubble_bg); // ViewGroupSetter headerHomeViewSetter = new ViewGroupSetter((ViewGroup) headerHomeView); -// headerHomeViewSetter.childViewBgColor(R.id.header_home, R.attr.bottombar_bg); // 这个不生效,反而会影响底色修改 +// headerHomeViewSetter.childViewBgColor(R.id.header_home, R.attr.root_view_bg); // 这个不生效,反而会影响底色修改 // headerHomeViewSetter.childViewTextColor(R.id.header_home_icon, R.attr.tag_slv_item_icon); // headerHomeViewSetter.childViewTextColor(R.id.header_home_title, R.attr.lv_item_title_color); // headerHomeViewSetter.childViewTextColor(R.id.header_home_count, R.attr.lv_item_desc_color); - - ViewGroupSetter headerPinnedViewSetter = new ViewGroupSetter((ViewGroup) headerPinnedView); - headerPinnedViewSetter.childViewTextColor(R.id.header_item_icon, R.attr.tag_slv_item_icon); - headerPinnedViewSetter.childViewTextColor(R.id.header_item_title, R.attr.lv_item_title_color); - headerPinnedViewSetter.childViewTextColor(R.id.header_item_count, R.attr.lv_item_desc_color); - headerPinnedViewSetter.childViewBgDrawable(R.id.header_item_count, R.attr.bubble_bg); - headerPinnedViewSetter.childViewBgColor(R.id.header_item, R.attr.bottombar_bg); +// ViewGroupSetter headerPinnedViewSetter = new ViewGroupSetter((ViewGroup) headerPinnedView); +// headerPinnedViewSetter.childViewBgColor(R.id.header_item, R.attr.root_view_bg); +// headerPinnedViewSetter.childViewTextColor(R.id.header_item_icon, R.attr.tag_slv_item_icon); +// headerPinnedViewSetter.childViewTextColor(R.id.header_item_title, R.attr.lv_item_title_color); +// headerPinnedViewSetter.childViewTextColor(R.id.header_item_count, R.attr.lv_item_desc_color); +// headerPinnedViewSetter.childViewBgDrawable(R.id.header_item_count, R.attr.bubble_bg); mColorfulBuilder // 这里做设置,实质都是直接生成了一个View(根据Activity的findViewById),并直接添加到 colorful 内的 mElements 中。 .backgroundColor(R.id.main_swipe_refresh, R.attr.root_view_bg) -// .backgroundColor(R.id.main_scroll_layout_bg, R.attr.bottombar_bg) -// .textColor(R.id.main_scroll_layout_title, R.attr.bottombar_fg) -// .backgroundColor(R.id.main_scrolllayout_divider, R.attr.bottombar_divider) - - - .backgroundColor(R.id.main_tag_list_view, R.attr.bottombar_bg) // 这个不生效 -// .backgroundColor(R.id.main_tag_bg, R.attr.bottombar_bg) // 这个不生效 - - .textColor(R.id.header_item_icon, R.attr.tag_slv_item_icon) - .textColor(R.id.header_item_title, R.attr.lv_item_title_color) - .textColor(R.id.header_item_count, R.attr.lv_item_desc_color) - .backgroundColor(R.id.header_item, R.attr.bottombar_bg) - // 设置 toolbar .backgroundColor(R.id.main_toolbar, R.attr.topbar_bg) - .textColor(R.id.main_toolbar_hint, R.attr.topbar_fg) -// .textColor(R.id.main_toolbar_readability, R.attr.topbar_fg) + //.textColor(R.id.main_toolbar_hint, R.attr.topbar_fg) + + .backgroundColor(R.id.sheet_tag, R.attr.root_view_bg) // 设置 bottombar .backgroundColor(R.id.main_bottombar, R.attr.bottombar_bg) // 设置中屏和底栏之间的分割线 .backgroundColor(R.id.main_bottombar_divider, R.attr.bottombar_divider) .textColor(R.id.main_bottombar_search, R.attr.bottombar_fg) -// .textColor(R.id.main_bottombar_articles_state, R.attr.bottombar_fg) -// .textColor(R.id.main_bottombar_star, R.attr.bottombar_fg) .textColor(R.id.main_bottombar_setting, R.attr.bottombar_fg) .textColor(R.id.main_bottombar_tag, R.attr.bottombar_fg) .textColor(R.id.main_bottombar_refresh_articles, R.attr.bottombar_fg) @@ -1398,10 +1413,11 @@ protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { // 设置 listview 背景色 // 这里做设置,实质是将View(根据Activity的findViewById),并直接添加到 colorful 内的 mElements 中。 .setter(relative) - .setter(headerPinnedViewSetter) +// .setter(headerPinnedViewSetter) // .setter(headerHomeViewSetter) - .setter(tagListViewSetter) - .setter(artListViewSetter); + .setter(articlesHeaderVS) + .setter(artListViewSetter) + .setter(tagListViewSetter); return mColorfulBuilder; } } diff --git a/app/src/main/java/me/wizos/loread/activity/MusicActivity.java b/app/src/main/java/me/wizos/loread/activity/MusicActivity.java new file mode 100644 index 0000000..5f15fad --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/MusicActivity.java @@ -0,0 +1,363 @@ +package me.wizos.loread.activity; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.text.TextUtils; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.BounceInterpolator; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.widget.Toolbar; + +import com.freedom.lauzy.playpauseviewlib.PlayPauseView; +import com.hjq.permissions.OnPermission; +import com.hjq.permissions.Permission; +import com.hjq.permissions.XXPermissions; +import com.hjq.toast.ToastUtils; +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.enums.PopupAnimation; +import com.lxj.xpopup.interfaces.OnSelectListener; +import com.noober.background.drawable.DrawableCreator; +import com.socks.library.KLog; +import com.yhao.floatwindow.constant.MoveType; +import com.yhao.floatwindow.constant.Screen; +import com.yhao.floatwindow.view.FloatWindow; + +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.service.MusicService; +import me.wizos.loread.utils.ScreenUtil; +import me.wizos.loread.utils.TimeUtil; +import me.wizos.loread.view.colorful.Colorful; + +public class MusicActivity extends BaseActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_music); + initToolbar(); + + Intent intent = getIntent(); + String playUrl = intent.getDataString(); + + // 补救,获取 playUrl + if (TextUtils.isEmpty(playUrl)) { + playUrl = intent.getStringExtra(Intent.EXTRA_TEXT); + } + String title = intent.getStringExtra("title"); + + KLog.e("获取到链接:" + title + playUrl); + + playConnection = new PlayConnection(); + intent = new Intent(this, MusicService.class); + + if (!TextUtils.isEmpty(playUrl)) { + intent.setData(Uri.parse(playUrl)); + intent.putExtra("title", title); + initFloatWindow(); + } + startService(intent); + bindService(intent, playConnection, BIND_AUTO_CREATE); + applyPermissions(); + } + + + private void applyPermissions() { + XXPermissions.with(this) + //.constantRequest() //可设置被拒绝后继续申请,直到用户授权或者永久拒绝 + .permission(Permission.SYSTEM_ALERT_WINDOW) //支持请求6.0悬浮窗权限8.0请求安装权限 + .request(new OnPermission() { + @Override + public void hasPermission(List granted, boolean isAll) { + } + + @Override + public void noPermission(List denied, boolean quick) { + for (String id : denied) { + KLog.e("无法获取权限" + id); + } + ToastUtils.show(getString(R.string.plz_grant_permission_tips)); + } + }); + } + @Override + protected void onDestroy() { + super.onDestroy(); + maHandler.removeCallbacksAndMessages(null); + if (playConnection != null) { + //退出应用后与service解除绑定 + unbindService(playConnection); + } + } + + @Override + protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + return mColorfulBuilder; + } + + private boolean isChangeProgress = false; + protected SeekBar seekBar; + protected TextView speedView; + protected TextView currTimeView; + protected TextView totalTimeView; + protected PlayPauseView playPauseView; + protected PlayConnection playConnection; + protected MusicService.MusicControlBinder musicControl; + + protected static Handler maHandler = new Handler(); + + protected Runnable progressTask = new Runnable() { + @Override + public void run() { + int currentPosition = musicControl.getCurrentPosition(); + int duration = musicControl.getDuration(); + + if (seekBar != null && !isChangeProgress) { + seekBar.setProgress(currentPosition); + seekBar.setSecondaryProgress(musicControl.getBufferedPercent() * duration / 100); + seekBar.setMax(duration); + } + if (currTimeView != null && !isChangeProgress) { + currTimeView.setText(TimeUtil.getTime(currentPosition)); + totalTimeView.setText(TimeUtil.getTime(duration)); + } + //KLog.e("进度:" + seekBar + ", " + currTimeView + " , " + duration + " = " + TimeUtil.getTime(duration)); + maHandler.postDelayed(progressTask, 1000); + } + }; + + public class PlayConnection implements ServiceConnection { + //服务启动完成后会进入到这个方法 + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + //获得service中的MyBinder + KLog.e("服务连接:onServiceConnected" + musicControl); + initView(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + } + + public void initToolbar() { + Toolbar toolbar = findViewById(R.id.music_toolbar); + setSupportActionBar(toolbar); + // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 + getSupportActionBar().setHomeButtonEnabled(true); + // 决定左上角图标的左侧是否有向左的小箭头,true 有小箭头 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setTitle(getString(R.string.music)); + toolbar.setTitle(getString(R.string.music)); + toolbar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MusicActivity.this.finish(); + } + }); + } + + public void initView(IBinder service) { + musicControl = (MusicService.MusicControlBinder) service; + ImageView closeView = findViewById(R.id.music_close); + TextView titleView = findViewById(R.id.music_title); + playPauseView = findViewById(R.id.music_play_pause_view); + currTimeView = findViewById(R.id.currTime); + totalTimeView = findViewById(R.id.totalTime); + seekBar = findViewById(R.id.progressBar); + speedView = findViewById(R.id.music_speed); + + titleView.setText(musicControl.getTitle()); + + if (musicControl.isPlaying()) { + playPauseView.play(); + maHandler.post(progressTask); + } else { + playPauseView.pause(); + currTimeView.setText(TimeUtil.getTime(musicControl.getCurrentPosition())); + totalTimeView.setText(TimeUtil.getTime(musicControl.getDuration())); + seekBar.setProgress(musicControl.getCurrentPosition()); + } + + + musicControl.setPlayStatusListener(new MusicService.PlayStatusListener() { + @Override + public void onPlay() { + playPauseView.play(); + musicControl.setSpeed(App.i().getUser().getAudioSpeed()); + maHandler.postDelayed(progressTask, 1000); + } + + @Override + public void onPause() { + playPauseView.pause(); + maHandler.removeCallbacks(progressTask); + } + + @Override + public void onEnd() { + playPauseView.pause(); + maHandler.removeCallbacks(progressTask); + MusicActivity.this.finish(); + } + + @Override + public void onError(String cause) { + ToastUtils.show("系统出错:" + cause); + playPauseView.pause(); + maHandler.removeCallbacks(progressTask); + } + }); + playPauseView.setPlayPauseListener(new PlayPauseView.PlayPauseListener() { + @Override + public void play() { + musicControl.play(); + maHandler.removeCallbacks(progressTask); + maHandler.postDelayed(progressTask, 1000); + } + + @Override + public void pause() { + maHandler.removeCallbacks(progressTask); + musicControl.pause(); + } + }); + + seekBar.setMax(musicControl.getDuration()); + seekBar.setProgress(musicControl.getCurrentPosition()); + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && currTimeView != null) { + currTimeView.setText(TimeUtil.getTime(progress)); + } + } + + //开始触摸进度条,停止更新进度条 + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + isChangeProgress = true; + } + + //停止触摸进度条 + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + isChangeProgress = false; + musicControl.seekTo(seekBar.getProgress()); + } + }); + + + closeView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + maHandler.removeCallbacks(progressTask); + // 关闭悬浮窗 + FloatWindow.destroy(); + // 关闭 serview + Intent intent2 = new Intent(MusicActivity.this, MusicService.class); + stopService(intent2); + // 关闭 activity + MusicActivity.this.finish(); + } + }); + + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + speedView.setVisibility(View.GONE); + } else { + speedView.setText(App.i().getUser().getAudioSpeed() + ""); + speedView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new XPopup.Builder(MusicActivity.this) + .isCenterHorizontal(true) //是否与目标水平居中对齐 + .offsetY(-10) + .hasShadowBg(true) + .popupAnimation(PopupAnimation.ScaleAlphaFromCenter) + .atView(speedView) // 依附于所点击的View,内部会自动判断在上方或者下方显示 + .asAttachList(new String[]{"0.8", "1.0", "1.2", "1.5", "2.0"}, + null, + new OnSelectListener() { + @Override + public void onSelect(int which, String text) { + musicControl.setSpeed(Float.parseFloat(text)); + User user = App.i().getUser(); + user.setAudioSpeed(Float.parseFloat(text)); + //App.i().getUserBox().put(user); + CoreDB.i().userDao().update(user); + speedView.setText(text); + } + }) + .show(); + } + }); + } + } + + + private void initFloatWindow() { + ImageView imageView = new ImageView(this); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setPadding(ScreenUtil.dp2px(10), ScreenUtil.dp2px(10), ScreenUtil.dp2px(10), ScreenUtil.dp2px(10)); + imageView.setImageDrawable(getDrawable(R.drawable.ic_music)); + + //imageView.setBackground(getDrawable(R.drawable.shape_corners)); + Drawable drawable = new DrawableCreator.Builder() +// .setUnPressedDrawable( getDrawable(R.color.bluePrimary) ) + .setRipple(true, getResources().getColor(R.color.primary)) + .setPressedSolidColor(getResources().getColor(R.color.primary), getResources().getColor(R.color.bluePrimary)) + .setSolidColor(getResources().getColor(R.color.bluePrimary)) + .setCornersRadius(ScreenUtil.dp2px(30)) + .build(); + imageView.setBackground(drawable); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getApplicationContext(), MusicActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + + + FloatWindow + .with(getApplicationContext()) + .setView(imageView) + .setWidth(Screen.width, 0.15f) //设置悬浮控件宽高 + .setHeight(Screen.width, 0.15f) + .setX(Screen.width, 0.8f) //设置控件初始位置 + .setY(Screen.height, 0.8f) + .setMoveType(MoveType.slide, 10, 10, 10, 10) + .setMoveStyle(500, new BounceInterpolator()) + .setFilter(true, MainActivity.class, ArticleActivity.class) + .setDesktopShow(false) + .build(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if( item.getItemId() == android.R.id.home ){ + this.finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/me/wizos/loread/activity/ProviderActivity.java b/app/src/main/java/me/wizos/loread/activity/ProviderActivity.java new file mode 100644 index 0000000..644cbb5 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/ProviderActivity.java @@ -0,0 +1,165 @@ +package me.wizos.loread.activity; + +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; + +import com.hjq.toast.ToastUtils; +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.impl.LoadingPopupView; +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.Contract; +import me.wizos.loread.R; +import me.wizos.loread.activity.login.LoginInoReaderActivity; +import me.wizos.loread.activity.login.LoginTinyRSSActivity; +import me.wizos.loread.bean.Token; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.network.api.FeedlyApi; +import me.wizos.loread.network.api.InoReaderApi; +import me.wizos.loread.network.api.OAuthApi; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.view.colorful.Colorful; + +public class ProviderActivity extends BaseActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if(Build.VERSION.SDK_INT < 23){ + setContentView(R.layout.activity_provider_low_version); + }else { + setContentView(R.layout.activity_provider); + } + } + + public void loginInoReader(View view){ + Intent intent = new Intent(this, LoginInoReaderActivity.class); + startActivityForResult(intent, App.ActivityResult_LoginPageToProvider); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + public void oauthInoReader(View view) { + Intent intent = new Intent(this, WebActivity.class); + intent.setData(Uri.parse(new InoReaderApi().getOAuthUrl())); + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + public void oauthFeedly(View view) { + Intent intent = new Intent(this, WebActivity.class); + intent.setData(Uri.parse(new FeedlyApi().getOAuthUrl())); + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + public void loginTinyRSS(View view) { + Intent intent = new Intent(this, LoginTinyRSSActivity.class); + startActivityForResult(intent, App.ActivityResult_LoginPageToProvider); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + +// // TODO: 2019/2/16 跳转到RSS添加的发现页 +// public void selectLocalRSS(View view) { +// String uid = Contract.PROVIDER_LOCALRSS + "_" + getString(R.string.app_id); +// User user = CoreDB.i().userDao().getById(uid); +// if ( user == null ){ +// user = new User(); +// user.setId(uid); +// user.setSource(Contract.PROVIDER_LOCALRSS); +// user.setUserId(getString(R.string.app_id)); +// user.setUserName(getString(R.string.app_name)); +// user.setExpiresTimestamp(0); +// CoreDB.i().userDao().insert(user); +// } +// +// App.i().getGlobalKV().putString(Contract.UID, uid); +// route(); +// } + + + private void getAccessToken(final String code, OAuthApi api) { + final LoadingPopupView dialog = new XPopup.Builder(this) + .dismissOnTouchOutside(false) + .asLoading(getString(R.string.authing)); + dialog.show(); + + api.getAccessToken(code, new CallbackX() { + @Override + public void onSuccess(Token token) { + KLog.e("授权为:" + token); + dialog.setTitle(getString(R.string.fetch_user_info)); + api.setAuthorization(token.getAuth()); + api.fetchUserInfo(new CallbackX() { + @Override + public void onSuccess(User user) { + KLog.e("用户资料:" + user + token.getAuth()); + user.setToken(token); + + App.i().getKeyValue().putString(Contract.UID, user.getId()); + App.i().setApi(api); + CoreDB.i().userDao().insert(user); + dialog.dismiss(); + KLog.e(token); + App.i().restartApp(); + } + + @Override + public void onFailure(String error) { + ToastUtils.show(getString(R.string.login_failure_please_try_again) + error); + dialog.dismiss(); + } + }); + } + + @Override + public void onFailure(String error) { + } + }); + } + + + @Override + protected void onNewIntent(Intent paramIntent) { + super.onNewIntent(paramIntent); + String url = paramIntent.getDataString(); + KLog.e("获取到数据:" + url); + if (TextUtils.isEmpty(url)) { + ToastUtils.show(getString(R.string.auth_failure_please_try_again)); + return; + } + Uri uri = Uri.parse(url); + String host = uri.getHost().toLowerCase(); + String code = uri.getQueryParameter("code"); + if (TextUtils.isEmpty(host)) { + ToastUtils.show(getString(R.string.auth_failure_please_try_again)); + return; + } + + if (TextUtils.isEmpty(code)) { + ToastUtils.show("无法获取code"); + return; + } + + if (host.contains("feedlyauth")) { + getAccessToken(code, new FeedlyApi()); + } else if (host.contains(Contract.PROVIDER_INOREADER.toLowerCase())) { + getAccessToken(code, new InoReaderApi()); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + KLog.e("---------" + resultCode + requestCode); + if (resultCode == App.ActivityResult_LoginPageToProvider) { + App.i().getUser(); + App.i().restartApp(); + } + } + + public Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + return mColorfulBuilder; + } +} diff --git a/app/src/main/java/me/wizos/loread/activity/RuleGenerateActivity.java b/app/src/main/java/me/wizos/loread/activity/RuleGenerateActivity.java new file mode 100644 index 0000000..b2cb14c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/RuleGenerateActivity.java @@ -0,0 +1,16 @@ +package me.wizos.loread.activity; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import me.wizos.loread.R; + +public class RuleGenerateActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_rule_generate); + } +} diff --git a/app/src/main/java/me/wizos/loread/activity/SearchActivity.java b/app/src/main/java/me/wizos/loread/activity/SearchActivity.java index a8eadbf..0356846 100644 --- a/app/src/main/java/me/wizos/loread/activity/SearchActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/SearchActivity.java @@ -3,7 +3,6 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -18,30 +17,30 @@ import android.widget.ListView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; -import com.google.gson.Gson; -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.model.Response; +import com.hjq.toast.ToastUtils; import com.socks.library.KLog; -import org.greenrobot.greendao.annotation.NotNull; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import me.wizos.loread.App; import me.wizos.loread.R; -import me.wizos.loread.bean.search.FeedlyFeed; -import me.wizos.loread.bean.search.FeedlyFeedsSearchResult; -import me.wizos.loread.bean.search.QuickAdd; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.db.Feed; -import me.wizos.loread.net.Api; -import me.wizos.loread.net.DataApi; -import me.wizos.loread.net.SearchApi; +import me.wizos.loread.bean.feedly.CategoryItem; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.bean.search.SearchFeedItem; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.network.callback.CallbackX; import me.wizos.loread.utils.TimeUtil; -import me.wizos.loread.utils.ToastUtil; import me.wizos.loread.view.IconFontView; import me.wizos.loread.view.SwipeRefreshLayoutS; import me.wizos.loread.view.colorful.Colorful; @@ -54,7 +53,7 @@ public class SearchActivity extends BaseActivity { private ListView listView; private SwipeRefreshLayoutS swipeRefreshLayoutS; - private ArrayList feedlyFeeds = new ArrayList<>(); + private ArrayList searchFeedItems = new ArrayList<>(); private SearchListViewAdapter listViewAdapter; private View wordHeaderView, resultCountHeaderView; private RequestOptions options; @@ -67,7 +66,7 @@ protected void onCreate(Bundle savedInstanceState) { initView(); options = new RequestOptions() .placeholder(R.mipmap.ic_launcher) - .circleCrop() + //.circleCrop() .centerCrop(); } @@ -88,14 +87,14 @@ private void initView() { wordHeaderView = getLayoutInflater().inflate(R.layout.activity_search_list_header_word, listView, false); resultCountHeaderView = getLayoutInflater().inflate(R.layout.activity_search_list_header_result_count, listView, false); - listViewAdapter = new SearchListViewAdapter(SearchActivity.this, feedlyFeeds); + listViewAdapter = new SearchListViewAdapter(SearchActivity.this, searchFeedItems); listView.setAdapter(listViewAdapter); searchView.requestFocus(); searchView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { -// KLog.e("输入前确认执行该方法", "开始输入:" + s .toString() + start + " " + after + " " + count); +// KLog.e("输入前确认执行该方法", "开始输入:" + s .toString() + startAnimation + " " + after + " " + count); } @Override @@ -104,7 +103,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { listView.removeHeaderView(wordHeaderView); listView.removeHeaderView(resultCountHeaderView); swipeRefreshLayoutS.setRefreshing(false); - feedlyFeeds.clear(); + searchFeedItems.clear(); } else if (listView.getHeaderViewsCount() == 0) { KLog.e("直接变成搜索该关键词"); listView.addHeaderView(wordHeaderView); @@ -142,101 +141,138 @@ public boolean onKey(View v, int keyCode, KeyEvent event) { } private void searchAndLoadFeedsData() { -// if( searchView.getText().toString().startsWith("http://")){ -// searchView.setText( searchView.getText().toString().replaceFirst("^http:\\/\\/","")); -// } swipeRefreshLayoutS.setRefreshing(true); listView.setEnabled(false); listView.removeHeaderView(wordHeaderView); listView.removeHeaderView(resultCountHeaderView); -// OkGo.cancelTag(); - SearchApi.i().asyncFetchSearchResult(searchView.getText().toString(), new StringCallback() { - @Override - public void onSuccess(Response response) { - try { - FeedlyFeedsSearchResult searchResult = new Gson().fromJson(response.body(), FeedlyFeedsSearchResult.class); - if (searchResult != null && searchResult.getResults() != null && searchResult.getResults().size() != 0) { - feedlyFeeds = searchResult.getResults(); -// KLog.e("点击搜索" + searchView.getText().toString() + feedlyFeeds.size()); -// ToastUtil.show("已获取到" + feedlyFeeds.size() + "个订阅源"); - } else { - feedlyFeeds = new ArrayList(); - } - listViewAdapter = new SearchListViewAdapter(SearchActivity.this, feedlyFeeds); - TextView textView = resultCountHeaderView.findViewById(R.id.search_feeds_result_count); - textView.setText(getString(R.string.search_cloudy_feeds_result_count, feedlyFeeds.size())); - listView.addHeaderView(resultCountHeaderView); - listView.setAdapter(listViewAdapter); - swipeRefreshLayoutS.setRefreshing(false); - listView.setEnabled(true); - } catch (Exception e) { - e.printStackTrace(); -// KLog.e("报错","失败了"); - onError(response); - } - } - @Override - public void onError(Response response) { - ToastUtil.showLong(App.i().getString(R.string.fail_try)); - swipeRefreshLayoutS.setRefreshing(false); - listView.setEnabled(true); - listView.addHeaderView(wordHeaderView); - } - }); +// Retrofit retrofit = new Retrofit.Builder() +// .baseUrl(FeedlyApi.HOST + "/") // 设置网络请求的Url地址, 必须以/结尾 +// .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 +// .client(HttpClientManager.i().simpleClient()) +// .build(); +// FeedlyService feedlyService = retrofit.create(FeedlyService.class); +// +// //对 发送请求 进行封装 +// Call callSearchFeeds = feedlyService.getSearchFeeds(searchView.getText().toString(), 100); +// callSearchFeeds.enqueue(new Callback() { +// @Override +// public void onResponse(Call call, retrofit2.Response response) { +// SearchFeeds searchResult = response.body(); +// KLog.e("成功:" + searchResult); +// if (searchResult != null && searchResult.getResults() != null && searchResult.getResults().size() != 0) { +// searchFeedItems = searchResult.getResults(); +// // KLog.e("点击搜索" + searchView.getText().toString() + searchFeedItems.size()); +// // ToastUtil.show("已获取到" + searchFeedItems.size() + "个订阅源"); +// } else { +// searchFeedItems = new ArrayList(); +// } +// listViewAdapter = new SearchListViewAdapter(SearchActivity.this, searchFeedItems); +// TextView textView = resultCountHeaderView.findViewById(R.id.search_feeds_result_count); +// textView.setText(getString(R.string.search_cloudy_feeds_result_count, searchFeedItems.size())); +// listView.addHeaderView(resultCountHeaderView); +// listView.setAdapter(listViewAdapter); +// swipeRefreshLayoutS.setRefreshing(false); +// listView.setEnabled(true); +// } +// +// @Override +// public void onFailure(Call call, Throwable t) { +// KLog.e("失败:" + t); +// ToastUtils.show(App.i().getString(R.string.fail_try)); +// swipeRefreshLayoutS.setRefreshing(false); +// listView.setEnabled(true); +// listView.addHeaderView(wordHeaderView); +// } +// }); } + private Integer[] selectIndices; - private void addFeed(final View view, final String feedId) { - view.setClickable(false); - DataApi.i().addFeed(feedId, new StringCallback() { - @Override - public void onSuccess(Response response) { - KLog.e(response.body()); - try { - QuickAdd quickAdd = new Gson().fromJson(response.body().trim(), QuickAdd.class); - if (quickAdd == null || quickAdd.getNumResults() == 0) { - this.onError(response); - return; + public void showSelectFolder(final View view, final String feedId) { + final List categoryList = CoreDB.i().categoryDao().getAll(App.i().getUser().getId()); + String[] categoryTitleArray = new String[categoryList.size()]; + for (int i = 0, size = categoryList.size(); i < size; i++) { + categoryTitleArray[i] = categoryList.get(i).getTitle(); + } + final EditFeed editFeed = new EditFeed(); + editFeed.setId(feedId); + new MaterialDialog.Builder(this) + .title(getString(R.string.select_category)) + .items(categoryTitleArray) + .alwaysCallMultiChoiceCallback() + .itemsCallbackMultiChoice(null, new MaterialDialog.ListCallbackMultiChoice() { + @Override + public boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text) { + SearchActivity.this.selectIndices = which; + for (int i : which) { + KLog.e("点选了:" + i); + } + return true; } - Feed feed = new Feed(); - feed.setId(quickAdd.getStreamId()); - feed.setTitle(quickAdd.getStreamName()); - feed.setHtmlurl(quickAdd.getStreamId().replaceFirst("^feed\\/", "")); - WithDB.i().addFeed(feed); - ((IconFontView) view).setText(R.string.font_tick); - view.setClickable(true); - } catch (Exception e) { - KLog.e(e); - this.onError(response); - return; - } - } + }) + .positiveText(R.string.confirm) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + ArrayList categoryItemList = new ArrayList<>(); + for (int i = 0; i < selectIndices.length; i++) { + CategoryItem categoryItem = new CategoryItem(); + categoryItem.setId(categoryList.get(selectIndices[i]).getId()); + categoryItemList.add(categoryItem); + } + editFeed.setCategoryItems(categoryItemList); + view.setClickable(false); + App.i().getApi().addFeed(editFeed, new CallbackX() { + @Override + public void onSuccess(Object result) { + KLog.e("添加成功"); + ((IconFontView) view).setText(R.string.font_tick); + ToastUtils.show(R.string.subscribe_success); + view.setClickable(true); + } - @Override - public void onError(Response response) { - ToastUtil.showLong(getString(R.string.toast_subscribe_fail)); - view.setClickable(true); - } - }); + @Override + public void onFailure(Object error) { + ToastUtils.show(getString(R.string.subscribe_fail)); + view.setClickable(true); + } + }); +// App.i().getApi().addFeed(editFeed).enqueue(new Callback() { +// @Override +// public void onResponse(Call call, retrofit2.Response response) { +// KLog.e("添加成功"); +// ((IconFontView) view).setText(R.string.font_tick); +// ToastUtils.show(R.string.subscribe_success); +// view.setClickable(true); +// } +// +// @Override +// public void onFailure(Call call, Throwable t) { +// ToastUtils.show(getString(R.string.subscribe_fail)); +// view.setClickable(true); +// } +// }); + } + }).show(); } - class SearchListViewAdapter extends ArrayAdapter { - private List feedlyFeeds; + class SearchListViewAdapter extends ArrayAdapter { + private List searchFeedItems; - public SearchListViewAdapter(Context context, List feedList) { + public SearchListViewAdapter(Context context, List feedList) { super(context, 0, feedList); - this.feedlyFeeds = feedList; + this.searchFeedItems = feedList; } @Override public int getCount() { - return feedlyFeeds.size(); + return searchFeedItems.size(); } @Override - public FeedlyFeed getItem(int position) { - return feedlyFeeds.get(position); + public SearchFeedItem getItem(int position) { + return searchFeedItems.get(position); } @Override @@ -246,7 +282,7 @@ public long getItemId(int position) { @Override public View getView(final int position, View convertView, @NotNull final ViewGroup parent) { - final FeedlyFeed feedlyFeed = this.getItem(position); + final SearchFeedItem searchFeedItem = this.getItem(position); if (convertView == null) { cvh = new CustomViewHolder(); convertView = LayoutInflater.from(SearchActivity.this).inflate(R.layout.activity_search_list_item_feed, null); @@ -261,26 +297,27 @@ public View getView(final int position, View convertView, @NotNull final ViewGro } else { cvh = (CustomViewHolder) convertView.getTag(); } - cvh.feedTitle.setText(feedlyFeed.getTitle()); - if (!TextUtils.isEmpty(feedlyFeed.getDescription())) { + cvh.feedTitle.setText(searchFeedItem.getTitle()); + if (!TextUtils.isEmpty(searchFeedItem.getDescription())) { cvh.feedSummary.setVisibility(View.VISIBLE); - cvh.feedSummary.setText(feedlyFeed.getDescription()); + cvh.feedSummary.setText(searchFeedItem.getDescription()); } else { cvh.feedSummary.setVisibility(View.GONE); cvh.feedSummary.setText(""); } // KLog.e("当前view是:" + position +" " + convertView.getId() + ""); -// Glide.with(SearchActivity.this).load(feedlyFeed.getVisualUrl()).centerCrop().into(cvh.feedIcon); - Glide.with(SearchActivity.this).load(feedlyFeed.getVisualUrl()).apply(options).into(cvh.feedIcon); +// Glide.with(SearchActivity.this).load(searchFeedItem.getVisualUrl()).centerCrop().into(cvh.feedIcon); + Glide.with(SearchActivity.this).load(searchFeedItem.getVisualUrl()).apply(options).into(cvh.feedIcon); - cvh.feedUrl.setText(feedlyFeed.getFeedId().replaceFirst("feed/", "")); - cvh.feedSubsVelocity.setText(getString(R.string.search_result_subs, feedlyFeed.getSubscribers(), feedlyFeed.getVelocity() + "")); - if (feedlyFeed.getLastUpdated() != 0) { - cvh.feedLastUpdated.setText(getString(R.string.search_result_last_update_time, TimeUtil.stampToTime(feedlyFeed.getLastUpdated(), "yyyy-MM-dd"))); + cvh.feedUrl.setText(searchFeedItem.getFeedId().replaceFirst("feed/", "")); + + cvh.feedSubsVelocity.setText( getResources().getQuantityString(R.plurals.search_result_followers, searchFeedItem.getSubscribers(), searchFeedItem.getSubscribers() ) + getString(R.string.search_result_articles, searchFeedItem.getVelocity()) ); + if (searchFeedItem.getLastUpdated() != 0) { + cvh.feedLastUpdated.setText(getString(R.string.search_result_last_update_time, TimeUtil.format(searchFeedItem.getLastUpdated(), "yyyy-MM-dd"))); } else { cvh.feedLastUpdated.setText(""); } - if (WithDB.i().getFeed(feedlyFeed.getFeedId()) != null) { + if (CoreDB.i().feedDao().getById(App.i().getUser().getId(), searchFeedItem.getFeedId()) != null) { cvh.feedSubState.setText(R.string.font_tick); } else { cvh.feedSubState.setText(R.string.font_add); @@ -288,30 +325,26 @@ public View getView(final int position, View convertView, @NotNull final ViewGro cvh.feedSubState.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { - if (WithDB.i().getFeed(feedlyFeed.getFeedId()) != null) { + if (CoreDB.i().feedDao().getById(App.i().getUser().getId(), searchFeedItem.getFeedId()) != null) { view.setClickable(false); // 防止重复点击 cvh.feedSubState.setText(R.string.font_tick); - DataApi.i().unsubscribeFeed(feedlyFeed.getFeedId(), new StringCallback() { + App.i().getApi().unsubscribeFeed(searchFeedItem.getFeedId(), new CallbackX() { @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - return; - } - WithDB.i().delFeed(feedlyFeed.getFeedId()); + public void onSuccess(Object result) { + CoreDB.i().feedDao().deleteById(App.i().getUser().getId(), searchFeedItem.getFeedId()); ((IconFontView) view).setText(R.string.font_add); view.setClickable(true); } @Override - public void onError(Response response) { - ToastUtil.showLong(getString(R.string.toast_unsubscribe_fail)); + public void onFailure(Object error) { + ToastUtils.show(getString(R.string.unsubscribe_failed,error)); view.setClickable(true); } }); } else { cvh.feedSubState.setText(R.string.font_add); - addFeed(view, feedlyFeed.getFeedId()); + showSelectFolder(view, searchFeedItem.getFeedId()); } } }); @@ -322,7 +355,7 @@ public void onError(Response response) { private CustomViewHolder cvh; - public class CustomViewHolder { + public static class CustomViewHolder { TextView feedTitle; TextView feedSummary; TextView feedUrl; @@ -344,7 +377,7 @@ private void initToolbar() { public void onSearchFeedsClicked(View view) { if (TextUtils.isEmpty(searchView.getText().toString())) { - ToastUtil.showShort("请输入要搜索的词"); + ToastUtils.show(R.string.please_input_keyword); return; } searchView.clearFocus(); @@ -357,20 +390,10 @@ public void onSearchLocalArtsClicked(View view) { Intent intent = new Intent(SearchActivity.this, MainActivity.class); KLog.e("要搜索的词是" + searchView.getText().toString()); intent.putExtra("searchWord", searchView.getText().toString()); - this.setResult(Api.ActivityResult_SearchLocalArtsToMain, intent); + this.setResult(App.ActivityResult_SearchLocalArtsToMain, intent); this.finish(); overridePendingTransition(R.anim.in_from_bottom, R.anim.out_from_bottom); } -// public void OnSubFeedClicked(View view){ -// Tool.showLong("此处要订阅源"); -// addFeed( "feed/" + searchView.getText().toString() ); -//// Intent intent = new Intent(SearchActivity.this, MainActivity.class); -//// KLog.e("要搜索的词是" + searchWord ); -//// intent.putExtra("searchWord", searchWord); -//// this.setResult(Api.ActivityResult_SearchLocalArtsToMain, intent); -//// this.finish(); -// } - @Override protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { diff --git a/app/src/main/java/me/wizos/loread/activity/SettingActivity.java b/app/src/main/java/me/wizos/loread/activity/SettingActivity.java index 22e0f61..af6a539 100644 --- a/app/src/main/java/me/wizos/loread/activity/SettingActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/SettingActivity.java @@ -1,31 +1,41 @@ package me.wizos.loread.activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.graphics.Color; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.Toolbar; -import android.text.InputType; import android.view.View; import android.widget.CompoundButton; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; + import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; +import com.afollestad.materialdialogs.simplelist.MaterialSimpleListAdapter; +import com.afollestad.materialdialogs.simplelist.MaterialSimpleListItem; +import com.hjq.toast.ToastUtils; import com.kyleduo.switchbutton.SwitchButton; import com.socks.library.KLog; +import java.util.List; + import butterknife.BindView; import butterknife.ButterKnife; import me.wizos.loread.App; import me.wizos.loread.BuildConfig; +import me.wizos.loread.Contract; import me.wizos.loread.R; -import me.wizos.loread.bean.config.GlobalConfig; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; import me.wizos.loread.view.colorful.Colorful; /** @@ -35,52 +45,6 @@ public class SettingActivity extends BaseActivity { protected static final String TAG = "SettingActivity"; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_setting); - ButterKnife.bind(this); - initToolbar(); - initView(); - } - - - @Override - protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { - mColorfulBuilder -// .backgroundDrawable(R.id.swipe_layout, R.attr.root_view_bg) - // 设置view的背景图片 - .backgroundColor(R.id.setting_coordinator, R.attr.root_view_bg) - // 设置 toolbar - .backgroundColor(R.id.setting_toolbar, R.attr.topbar_bg) -// .textColor(R.id.setting_toolbar_count, R.attr.topbar_fg) - // 设置文章信息 -// .textColor(R.id.setting_sync_first_open_title, R.attr.setting_title) -// .textColor(R.id.setting_sync_all_starred_title, R.attr.setting_title) -// .textColor(R.id.setting_sync_frequency_title, R.attr.setting_title) -// .textColor(R.id.setting_sync_frequency_summary, R.attr.setting_tips) - .textColor(R.id.setting_clear_day_title, R.attr.setting_title) - .textColor(R.id.setting_clear_day_summary, R.attr.setting_tips) - .textColor(R.id.setting_down_img_title, R.attr.setting_title) - - .textColor(R.id.setting_proxy_title, R.attr.setting_title) - -// .textColor(R.id.setting_scroll_mark_title, R.attr.setting_title) -// .textColor(R.id.setting_scroll_mark_tips, R.attr.setting_tips) -// .textColor(R.id.setting_order_tagfeed_title, R.attr.setting_title) -// .textColor(R.id.setting_order_tagfeed_tips, R.attr.setting_tips) -// .textColor(R.id.setting_link_open_mode_tips, R.attr.setting_tips) -// .textColor(R.id.setting_cache_path_starred_title, R.attr.setting_title) -// .textColor(R.id.setting_cache_path_starred_summary, R.attr.setting_tips) - .textColor(R.id.setting_link_open_mode_title, R.attr.setting_title) - .textColor(R.id.setting_license_title, R.attr.setting_title) - .textColor(R.id.setting_license_summary, R.attr.setting_tips) - .textColor(R.id.setting_about_title, R.attr.setting_title) - .textColor(R.id.setting_about_summary, R.attr.setting_tips); - return mColorfulBuilder; - } - - private TextView clearBeforeDaySummary; @Nullable @@ -89,11 +53,8 @@ protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { @Nullable @BindView(R.id.setting_auto_sync_on_wifi_sb) SwitchButton autoSyncOnWifiSB; - - @BindView(R.id.setting_auto_sync_on_wifi) View autoSyncOnWifi; - @Nullable @BindView(R.id.setting_auto_sync_frequency) View autoSyncFrequency; @@ -101,64 +62,26 @@ protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { @BindView(R.id.setting_sync_frequency_summary) TextView autoSyncFrequencySummary; -// TextView startTimeTextView; -// TextView endTimeTextView; - - @BindView(R.id.setting_backup) - TextView backup; - - @BindView(R.id.setting_restore) - TextView restore; - - @BindView(R.id.setting_read_config) - TextView readConfig; - - -// @OnClick(R.id.setting_night_time_interval) -// public void onClickNightTimeInterval(View view){ -// MaterialDialog dialog = new MaterialDialog.Builder(this) -// .title("夜间时间段") -// .customView(R.layout.select_night_time_interval_view, true) -// .positiveText("确认") -// .negativeText("取消") -// .onPositive(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -// WithPref.i().setNightThemeThemeStartTime(startTime); -// WithPref.i().setNightThemeEndTime(endTime); -// } -// }).build(); -// dialog.show(); -// -// startTimeTextView = (TextView) dialog.findViewById(R.id.start); -// endTimeTextView = (TextView) dialog.findViewById(R.id.end); -// -// CircleAlarmTimerView circleAlarmTimerView = (CircleAlarmTimerView) dialog.findViewById(R.id.circletimerview); -// circleAlarmTimerView.setOnTimeChangedListener(new CircleAlarmTimerView.OnTimeChangedListener() { -// @Override -// public void start(String starting) { -// startTimeTextView.setText(starting); -// startTime = starting; -// } -// @Override -// public void end(String ending) { -// endTimeTextView.setText(ending); -// endTime = ending; -// } -// }); -// -// circleAlarmTimerView.getPaddingStart(); -// } -// private String startTime; -// private String endTime; + @BindView(R.id.setting_lab) + TextView lab; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_setting); + ButterKnife.bind(this); + initToolbar(); + initView(); + } private void toggleAutoSyncItem() { - if (WithPref.i().isAutoSync()) { + if (App.i().getUser().isAutoSync()) { autoSyncSB.setChecked(true); autoSyncOnWifi.setVisibility(View.VISIBLE); autoSyncFrequency.setVisibility(View.VISIBLE); - autoSyncOnWifiSB.setChecked(WithPref.i().isAutoSyncOnWifi()); + autoSyncOnWifiSB.setChecked(App.i().getUser().isAutoSyncOnlyWifi()); } else { autoSyncSB.setChecked(false); autoSyncOnWifi.setVisibility(View.GONE); @@ -166,25 +89,30 @@ private void toggleAutoSyncItem() { } } - private void initView(){ + private void initView() { SwitchButton downImgWifi, sysBrowserOpenLink, autoToggleTheme; +// autoSyncSB = findViewById(R.id.setting_auto_sync_sb); +// autoSyncOnWifi.findViewById(R.id.setting_auto_sync_on_wifi_sb); +// autoSyncFrequency.findViewById(R.id.setting_auto_sync_on_wifi_sb); toggleAutoSyncItem(); autoSyncSB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - WithPref.i().setAutoSync(b); + User user = App.i().getUser(); + user.setAutoSync(b); + CoreDB.i().userDao().update(user); toggleAutoSyncItem(); } }); downImgWifi = findViewById(R.id.setting_down_img_sb); - downImgWifi.setChecked(WithPref.i().isDownImgOnlyWifi()); + downImgWifi.setChecked(App.i().getUser().isDownloadImgOnlyWifi()); clearBeforeDaySummary = findViewById(R.id.setting_clear_day_summary); - clearBeforeDaySummary.setText(getResources().getString(R.string.clear_day_summary, String.valueOf(WithPref.i().getClearBeforeDay()))); + clearBeforeDaySummary.setText(getResources().getString(R.string.clear_day_summary, String.valueOf(App.i().getUser().getCachePeriod()))); - int autoSyncFrequency = WithPref.i().getAutoSyncFrequency(); + int autoSyncFrequency = App.i().getUser().getAutoSyncFrequency(); if (autoSyncFrequency >= 60) { autoSyncFrequencySummary.setText(getResources().getString(R.string.xx_hour, autoSyncFrequency / 60 + "")); @@ -192,16 +120,10 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { autoSyncFrequencySummary.setText(getResources().getString(R.string.xx_minute, autoSyncFrequency + "")); } sysBrowserOpenLink = findViewById(R.id.setting_link_open_mode_sb); - sysBrowserOpenLink.setChecked(WithPref.i().isSysBrowserOpenLink()); -// View openLinkMode = findViewById(R.id.setting_link_open_mode); -// if (!BuildConfig.DEBUG) { -// openLinkMode.setVisibility(View.GONE); -// } + sysBrowserOpenLink.setChecked(App.i().getUser().isOpenLinkBySysBrowser()); autoToggleTheme = findViewById(R.id.setting_auto_toggle_theme_sb); - autoToggleTheme.setChecked(WithPref.i().isAutoToggleTheme()); - -// clearLog = (Button)findViewById(R.id.setting_clear_log_button); + autoToggleTheme.setChecked(App.i().getUser().isAutoToggleTheme()); TextView versionSummary = findViewById(R.id.setting_about_summary); PackageManager manager = this.getPackageManager(); @@ -216,91 +138,44 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if (BuildConfig.DEBUG) { -// TextView handleSavedPages = findViewById(R.id.setting_handle_saved_pages); -// handleSavedPages.setVisibility(View.VISIBLE); - - backup.setVisibility(View.VISIBLE); - restore.setVisibility(View.VISIBLE); - readConfig.setVisibility(View.VISIBLE); + lab.setVisibility(View.VISIBLE); + lab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SettingActivity.this, LabActivity.class); + startActivity(intent); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + } + }); } } -// private void getClearBeforeDayIndex(){ -//// CharSequence[] items = this.getResources().getTextArray(R.array.setting_clear_day_dialog_item_array); -// final int[] dayValueItems = this.getResources().getIntArray(R.array.setting_clear_day_dialog_item_array); -// int num = dayValueItems.length; -// for(int i=0; i< num; i++){ -// if ( clearBeforeDay == dayValueItems[i] ){ -// clearBeforeDayIndex = i; -// } -// } -// KLog.i( "读取默认的选项"+ clearBeforeDayIndex ); -// } - - - public void onSBClick(View view){ - SwitchButton v = (SwitchButton)view; + public void onSBClick(View view) { + SwitchButton v = (SwitchButton) view; KLog.i("点击"); + + User user = App.i().getUser(); switch (v.getId()) { case R.id.setting_link_open_mode_sb: - WithPref.i().setSysBrowserOpenLink(v.isChecked()); + user.setOpenLinkBySysBrowser(v.isChecked()); break; case R.id.setting_auto_toggle_theme_sb: - WithPref.i().setAutoToggleTheme(v.isChecked()); + user.setAutoToggleTheme(v.isChecked()); break; case R.id.setting_down_img_sb: - WithPref.i().setDownImgWifi(v.isChecked()); - break; - case R.id.setting_proxy_sb: - WithPref.i().setInoreaderProxy(v.isChecked()); - if (v.isChecked()) { - showInoreaderProxyHostDialog(); - } else { -// App.i().readHost(); - } + user.setDownloadImgOnlyWifi(v.isChecked()); break; -// case R.id.setting_sync_all_starred_sb_flyme: -// WithPref.i().setSyncAllStarred(v.isChecked()); -// syncAllStarred(); -// break; -// case R.id.setting_order_tagfeed_sb_flyme: -// WithPref.i().setOrderTagFeed(v.isChecked()); -// break; -// case R.id.setting_scroll_mark_sb_flyme: -// WithPref.i().setScrollMark(v.isChecked()); -// break; -// case R.id.setting_left_right_slide_sb_flyme: -// WithPref.i().setLeftRightSlideArticle(v.isChecked()); -// break; default: break; } -// KLog.i("Switch: " , v.isChecked() ); - } - - - private void showInoreaderProxyHostDialog() { - new MaterialDialog.Builder(this) - .title(R.string.setting_proxy_title) -// .content(R.string.setting_inoreader_proxy_dialog_title) - .inputType(InputType.TYPE_CLASS_TEXT) - .inputRange(8, 34) - .input(null, WithPref.i().getInoreaderProxyHost(), new MaterialDialog.InputCallback() { - @Override - public void onInput(MaterialDialog dialog, CharSequence input) { - WithPref.i().setInoreaderProxyHost(input.toString()); -// App.i().readHost(); - } - }) - .positiveText("保存") - .show(); + CoreDB.i().userDao().update(user); } public void onClickAutoSyncFrequencySelect(View view) { final int[] minuteArray = this.getResources().getIntArray(R.array.setting_sync_frequency_minute); - int preSelectTimeFrequency = WithPref.i().getAutoSyncFrequency(); + int preSelectTimeFrequency = App.i().getUser().getAutoSyncFrequency(); int preSelectTimeFrequencyIndex = -1; int num = minuteArray.length; @@ -308,7 +183,7 @@ public void onClickAutoSyncFrequencySelect(View view) { for (int i = 0; i < num; i++) { if (minuteArray[i] >= 60) { timeDescItems[i] = getResources().getString(R.string.xx_hour, minuteArray[i] / 60 + ""); - }else { + } else { timeDescItems[i] = getResources().getString(R.string.xx_minute, minuteArray[i] + ""); } if (preSelectTimeFrequency == minuteArray[i]) { @@ -324,7 +199,11 @@ public void onClickAutoSyncFrequencySelect(View view) { .itemsCallbackSingleChoice(preSelectTimeFrequencyIndex, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { - WithPref.i().setAutoSyncFrequency(minuteArray[which]); + User user = App.i().getUser(); + user.setAutoSyncFrequency(minuteArray[which]); + //App.i().getUserBox().put(user); + CoreDB.i().userDao().update(user); + KLog.i("选择了" + which); autoSyncFrequencySummary.setText(timeDescArray[which]); dialog.dismiss(); @@ -336,7 +215,7 @@ public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequ public void showClearBeforeDay(View view) { final int[] dayValueArray = this.getResources().getIntArray(R.array.setting_clear_day_dialog_item_array); - int preSelectClearBeforeDay = WithPref.i().getClearBeforeDay(); + int preSelectClearBeforeDay = App.i().getUser().getCachePeriod(); int preSelectClearBeforeDayIndex = -1; int num = dayValueArray.length; @@ -356,7 +235,11 @@ public void showClearBeforeDay(View view) { .itemsCallbackSingleChoice(preSelectClearBeforeDayIndex, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { - WithPref.i().setClearBeforeDay(dayValueArray[which]); + User user = App.i().getUser(); + user.setCachePeriod(dayValueArray[which]); +// App.i().getUserBox().put(user); + CoreDB.i().userDao().update(user); + clearBeforeDaySummary.setText(dayDescArray[which]); KLog.i("选择了" + which); dialog.dismiss(); @@ -396,86 +279,125 @@ public void showAbout(View view) { * @param view 对应的控件 * @return 返回true表示呼起手Q成功,返回fals表示呼起失败 ******************/ - public boolean joinQQGroup(View view) { + public void joinQQGroup(View view) { Intent intent = new Intent(); intent.setData(Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D" + "XPc8IGwXCDTPXItxM33eog5QLpLFdDrf")); - // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 + // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) try { startActivity(intent); - return true; } catch (Exception e) { // 未安装手Q或安装的版本不支持 - return false; + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData mClipData = ClipData.newPlainText("QQ", "106211435"); + // 将ClipData内容放到系统剪贴板里。 + assert cm != null; + cm.setPrimaryClip(mClipData); + ToastUtils.show(getString(R.string.copy_success)); } } + public void addAccount() { + Intent intent = new Intent(this, ProviderActivity.class); + startActivity(intent); + finish(); + overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); + } - private MaterialDialog materialDialog; - - public void onClickEsc(View view) { + public void escAccount() { new MaterialDialog.Builder(SettingActivity.this) - .content("确定要退出账号吗?\n退出后所有数据将被删除!") - .negativeText("取消") + .content(R.string.do_you_want_to_delete_data_of_this_account_after_logout) + .neutralText(R.string.cancel) + .negativeText(R.string.disagree) .positiveText(R.string.agree) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + dialog.dismiss(); + App.i().getKeyValue().remove(Contract.UID); App.i().clearApiData(); - Intent intent = new Intent(SettingActivity.this, LoginActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = new Intent(SettingActivity.this, ProviderActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + SettingActivity.this.finish(); + overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); + } + }) + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + dialog.dismiss(); + App.i().getKeyValue().remove(Contract.UID); + Intent intent = new Intent(SettingActivity.this, ProviderActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); SettingActivity.this.finish(); overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); } }) + .onNeutral(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + dialog.dismiss(); + } + }) .show(); } - - public void onClickBackup(View view) { - materialDialog = new MaterialDialog.Builder(this) - .title("正在处理") - .content("请耐心等待下") - .progress(true, 0) - .canceledOnTouchOutside(false) - .progressIndeterminateStyle(false) - .show(); - new Thread(new Runnable() { + public void onClickSwitchUser(View view) { + final List users = CoreDB.i().userDao().loadAll(); + KLog.i("点击切换账号:" + users ); + // 弹窗的适配器 + MaterialSimpleListAdapter adapter = new MaterialSimpleListAdapter(new MaterialSimpleListAdapter.Callback() { @Override - public void run() { - FileUtil.backup(); - materialDialog.dismiss(); + public void onMaterialListItemSelected(MaterialDialog dialog, int index, MaterialSimpleListItem item) { + dialog.dismiss(); + int count = users.size(); + if (index < count) { + App.i().getKeyValue().putString(Contract.UID, users.get(index).getId()); + App.i().restartApp(); + } else if (index == count) { + addAccount(); + } else if (index == count + 1) { + escAccount(); + } } - }).start(); - - } - - public void onClickRestore(View view) { - materialDialog = new MaterialDialog.Builder(this) - .title("正在处理") - .content("请耐心等待下") - .progress(true, 0) - .canceledOnTouchOutside(false) - .progressIndeterminateStyle(false) - .show(); - new Thread(new Runnable() { - @Override - public void run() { - FileUtil.restore(); - materialDialog.dismiss(); + }); + int iconRefs = R.drawable.ic_rename; + for (User user : users) { + switch (user.getSource()) { + case Contract.PROVIDER_FEEDLY: + iconRefs = R.drawable.logo_feedly; + break; + case Contract.PROVIDER_INOREADER: + iconRefs = R.drawable.logo_inoreader; + break; + case Contract.PROVIDER_TINYRSS: + iconRefs = R.drawable.logo_tinytinyrss; + break; } - }).start(); - } - public void onClickReadConfig(View view) { - materialDialog = new MaterialDialog.Builder(this) - .content("正在读取") - .progress(true, 0) - .canceledOnTouchOutside(false) - .progressIndeterminateStyle(false) + adapter.add(new MaterialSimpleListItem.Builder(SettingActivity.this) + .content( user.getUserName()) + .icon(iconRefs) + .backgroundColor(Color.TRANSPARENT) + .build()); + } + + adapter.add(new MaterialSimpleListItem.Builder(SettingActivity.this) + .content(R.string.add_account) + // .icon(R.drawable.ic_rename) + .backgroundColor(Color.TRANSPARENT) + .build()); + adapter.add(new MaterialSimpleListItem.Builder(SettingActivity.this) + .content(R.string.esc_account) + // .icon(R.drawable.ic_rename) + .backgroundColor(Color.TRANSPARENT) + .build()); + new MaterialDialog.Builder(this) + .title(R.string.switch_account) + .adapter(adapter, new LinearLayoutManager(this)) .show(); -// App.i().initFeedsConfig(); - GlobalConfig.i().reInit(); - materialDialog.dismiss(); } private void initToolbar() { @@ -494,4 +416,39 @@ public void onClickFeedback(View view) { startActivity(intent); overridePendingTransition(R.anim.fade_in, R.anim.fade_out); } + + + + @Override + protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + mColorfulBuilder +// .backgroundDrawable(R.id.swipe_layout, R.attr.root_view_bg) + // 设置view的背景图片 + .backgroundColor(R.id.setting_coordinator, R.attr.root_view_bg) + // 设置 toolbar + .backgroundColor(R.id.setting_toolbar, R.attr.topbar_bg) +// .textColor(R.id.setting_toolbar_count, R.attr.topbar_fg) + // 设置文章信息 +// .textColor(R.id.setting_sync_first_open_title, R.attr.setting_title) +// .textColor(R.id.setting_sync_all_starred_title, R.attr.setting_title) +// .textColor(R.id.setting_sync_frequency_title, R.attr.setting_title) +// .textColor(R.id.setting_sync_frequency_summary, R.attr.setting_tips) + .textColor(R.id.setting_clear_day_title, R.attr.setting_title) + .textColor(R.id.setting_clear_day_summary, R.attr.setting_tips) + .textColor(R.id.setting_down_img_title, R.attr.setting_title) + +// .textColor(R.id.setting_scroll_mark_title, R.attr.setting_title) +// .textColor(R.id.setting_scroll_mark_tips, R.attr.setting_tips) +// .textColor(R.id.setting_order_tagfeed_title, R.attr.setting_title) +// .textColor(R.id.setting_order_tagfeed_tips, R.attr.setting_tips) +// .textColor(R.id.setting_link_open_mode_tips, R.attr.setting_tips) +// .textColor(R.id.setting_cache_path_starred_title, R.attr.setting_title) +// .textColor(R.id.setting_cache_path_starred_summary, R.attr.setting_tips) + .textColor(R.id.setting_link_open_mode_title, R.attr.setting_title) + .textColor(R.id.setting_license_title, R.attr.setting_title) + .textColor(R.id.setting_license_summary, R.attr.setting_tips) + .textColor(R.id.setting_about_title, R.attr.setting_title) + .textColor(R.id.setting_about_summary, R.attr.setting_tips); + return mColorfulBuilder; + } } diff --git a/app/src/main/java/me/wizos/loread/activity/SplashActivity.java b/app/src/main/java/me/wizos/loread/activity/SplashActivity.java index 280e2d6..942de82 100644 --- a/app/src/main/java/me/wizos/loread/activity/SplashActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/SplashActivity.java @@ -4,8 +4,11 @@ import android.os.Bundle; import android.text.TextUtils; +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.Contract; import me.wizos.loread.R; -import me.wizos.loread.data.WithPref; import me.wizos.loread.view.colorful.Colorful; public class SplashActivity extends BaseActivity { @@ -14,9 +17,9 @@ public class SplashActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); -// setContentView(R.layout.activity_splash); - // 避免从桌面启动程序后,会重新实例化入口类的activity;https://blog.csdn.net/gufeilong/article/details/72900365 + // 0.防止在该app已经启动的情况下,点击桌面图标还会再次进入该页面 // 例如第一次点击图标启动应用是启动首界面A,然后进入第二个界面B;按home键后,再次点击图标,进入的页面是A + // https://blog.csdn.net/gufeilong/article/details/72900365 if (!this.isTaskRoot()) { Intent intent = getIntent(); if (intent != null) { @@ -27,20 +30,27 @@ protected void onCreate(Bundle savedInstanceState) { } } } - - if (TextUtils.isEmpty(WithPref.i().getAuth())) { - goTo(LoginActivity.TAG); - }else { -// KLog.e("当前的验证:" + WithPref.i().getAuth() + " " + App.UserID); - goTo(MainActivity.TAG); +// // 1.检查是否需要更新,是则跳转只更新页面/弹出更新Dialog +// // 2.获取已登录的服务商 +// // 2.1 未登录则跳转登录页(服务商选择页) +// // 2.2 检查登录授权时间,超时则跳转对应服务商的重新授权页 +// // 2.3 加载一些相关参数、配置到内存?还是在用到的时候再去加载? +// // 3.跳转至文章列表页 + Intent intent; + String uid = App.i().getKeyValue().getString(Contract.UID, null); + KLog.e("获取UID:" + uid ); + if ( TextUtils.isEmpty(uid) ) { + intent = new Intent(this, ProviderActivity.class); + } else { + intent = new Intent(this, MainActivity.class); } - this.finish(); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + finish(); } - - @Override - protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + public Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { return mColorfulBuilder; } } diff --git a/app/src/main/java/me/wizos/loread/activity/TTSActivity.java b/app/src/main/java/me/wizos/loread/activity/TTSActivity.java new file mode 100644 index 0000000..92e20eb --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/TTSActivity.java @@ -0,0 +1,323 @@ +package me.wizos.loread.activity; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.IBinder; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.BounceInterpolator; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.widget.Toolbar; + +import com.freedom.lauzy.playpauseviewlib.PlayPauseView; +import com.hjq.toast.ToastUtils; +import com.noober.background.drawable.DrawableCreator; +import com.socks.library.KLog; +import com.yhao.floatwindow.constant.MoveType; +import com.yhao.floatwindow.constant.Screen; +import com.yhao.floatwindow.view.FloatWindow; + +import me.wizos.loread.R; +import me.wizos.loread.service.AudioService; +import me.wizos.loread.utils.ScreenUtil; +import me.wizos.loread.view.colorful.Colorful; + +public class TTSActivity extends BaseActivity { + int articleNo; + boolean isQueue; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_tts); + initToolbar(); + initFloatWindow(); + Intent intent = getIntent(); + articleNo = intent.getIntExtra("articleNo", 0); + isQueue = intent.getBooleanExtra("isQueue",false); + + // Broadcast + KLog.e("获取到要播报:" + isQueue); + + playConnection = new PlayConnection(); + intent = new Intent(this, AudioService.class); + intent.putExtra("articleNo", articleNo); + intent.putExtra("isQueue",isQueue); + startService(intent); + bindService(intent, playConnection, BIND_AUTO_CREATE); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + //maHandler.removeCallbacksAndMessages(null); + if (playConnection != null) { + //退出应用后与service解除绑定 + unbindService(playConnection); + } + } + + private boolean isChangeProgress = false; + protected SeekBar seekBar; + protected TextView speedView; + protected TextView currTimeView; + protected TextView totalTimeView; + protected PlayPauseView playPauseView; + protected PlayConnection playConnection; + protected AudioService.AudioControlBinder audioControl; + +// protected static Handler maHandler = new Handler(); +// protected Runnable progressTask = new Runnable() { +// @Override +// public void run() { +// int currentPosition = musicControl.getCurrentPosition(); +// int duration = musicControl.getDuration(); +// +// if (seekBar != null && !isChangeProgress) { +// seekBar.setProgress(currentPosition); +// seekBar.setSecondaryProgress(musicControl.getBufferedPercent() * duration / 100); +// seekBar.setMax(duration); +// } +// if (currTimeView != null && !isChangeProgress) { +// currTimeView.setText(TimeUtil.getTime(currentPosition)); +// totalTimeView.setText(TimeUtil.getTime(duration)); +// } +// //KLog.e("进度:" + seekBar + ", " + currTimeView + " , " + duration + " = " + TimeUtil.getTime(duration)); +// maHandler.postDelayed(progressTask, 1000); +// } +// }; + + public class PlayConnection implements ServiceConnection { + //服务启动完成后会进入到这个方法 + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + //获得service中的MyBinder + KLog.e("服务连接:onServiceConnected, musicControl: " + audioControl); + initView(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + KLog.e("服务断开连接:onServiceDisconnected, musicControl: " + audioControl); + } + } + + public void initToolbar() { + Toolbar toolbar = findViewById(R.id.music_toolbar); + setSupportActionBar(toolbar); + // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 + getSupportActionBar().setHomeButtonEnabled(true); + // 决定左上角图标的左侧是否有向左的小箭头,true 有小箭头 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setTitle(getString(R.string.music)); + toolbar.setTitle(getString(R.string.music)); + toolbar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TTSActivity.this.finish(); + } + }); + } + + public void initView(IBinder service) { + audioControl = (AudioService.AudioControlBinder) service; + ImageView closeView = findViewById(R.id.music_close); + TextView titleView = findViewById(R.id.music_title); + playPauseView = findViewById(R.id.music_play_pause_view); + currTimeView = findViewById(R.id.currTime); + totalTimeView = findViewById(R.id.totalTime); + seekBar = findViewById(R.id.progressBar); + speedView = findViewById(R.id.music_speed); + + titleView.setText(audioControl.getTitle()); + + if (audioControl.isPlaying()) { + playPauseView.play(); + //maHandler.post(progressTask); + } else { + playPauseView.pause(); +// currTimeView.setText(TimeUtil.getTime(musicControl.getCurrentPosition())); +// totalTimeView.setText(TimeUtil.getTime(musicControl.getDuration())); +// seekBar.setProgress(musicControl.getCurrentPosition()); + } + + + audioControl.setPlayStatusListener(new AudioService.PlayStatusListener() { + @Override + public void onPlay() { + playPauseView.play(); +// musicControl.setSpeed(App.i().getUser().getAudioSpeed()); +// maHandler.postDelayed(progressTask, 1000); + } + + @Override + public void onPause() { + playPauseView.pause(); +// maHandler.removeCallbacks(progressTask); + } + + @Override + public void onEnd() { + playPauseView.pause(); +// maHandler.removeCallbacks(progressTask); + TTSActivity.this.finish(); + } + + @Override + public void onError(String cause) { + ToastUtils.show("系统出错:" + cause); + playPauseView.pause(); + //maHandler.removeCallbacks(progressTask); + } + }); + playPauseView.setPlayPauseListener(new PlayPauseView.PlayPauseListener() { + @Override + public void play() { + audioControl.play(); +// maHandler.removeCallbacks(progressTask); +// maHandler.postDelayed(progressTask, 1000); + } + + @Override + public void pause() { +// maHandler.removeCallbacks(progressTask); + audioControl.pause(); + } + }); + +// seekBar.setMax(musicControl.getDuration()); +// seekBar.setProgress(musicControl.getCurrentPosition()); +// seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { +// @Override +// public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { +// if (fromUser && currTimeView != null) { +// currTimeView.setText(TimeUtil.getTime(progress)); +// } +// } +// +// //开始触摸进度条,停止更新进度条 +// @Override +// public void onStartTrackingTouch(SeekBar seekBar) { +// isChangeProgress = true; +// } +// +// //停止触摸进度条 +// @Override +// public void onStopTrackingTouch(SeekBar seekBar) { +// isChangeProgress = false; +// musicControl.seekTo(seekBar.getProgress()); +// } +// }); + + + closeView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //maHandler.removeCallbacks(progressTask); + // 关闭悬浮窗 + FloatWindow.destroy(); + // 关闭 serview + Intent intent2 = new Intent(TTSActivity.this, AudioService.class); + stopService(intent2); + // 关闭 activity + TTSActivity.this.finish(); + } + }); + + +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { +// speedView.setVisibility(View.GONE); +// } else { +// speedView.setText(App.i().getUser().getAudioSpeed() + ""); +// speedView.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// new XPopup.Builder(Music1Activity.this) +// .isCenterHorizontal(true) //是否与目标水平居中对齐 +// .offsetY(-10) +// .hasShadowBg(true) +// .popupAnimation(PopupAnimation.ScaleAlphaFromCenter) +// .atView(speedView) // 依附于所点击的View,内部会自动判断在上方或者下方显示 +// .asAttachList(new String[]{"0.8", "1.0", "1.2", "1.5", "2.0"}, +// null, +// new OnSelectListener() { +// @Override +// public void onSelect(int which, String text) { +// musicControl.setSpeed(Float.parseFloat(text)); +// User user = App.i().getUser(); +// user.setAudioSpeed(Float.parseFloat(text)); +// //App.i().getUserBox().put(user); +// CoreDB.i().userDao().update(user); +// speedView.setText(text); +// } +// }) +// .show(); +// } +// }); +// } + } + + + private void initFloatWindow() { + ImageView imageView = new ImageView(this); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setPadding(ScreenUtil.dp2px(10), ScreenUtil.dp2px(10), ScreenUtil.dp2px(10), ScreenUtil.dp2px(10)); + imageView.setImageDrawable(getDrawable(R.drawable.ic_music)); + + //imageView.setBackground(getDrawable(R.drawable.shape_corners)); + Drawable drawable = new DrawableCreator.Builder() +// .setUnPressedDrawable( getDrawable(R.color.bluePrimary) ) + .setRipple(true, getResources().getColor(R.color.primary)) + .setPressedSolidColor(getResources().getColor(R.color.primary), getResources().getColor(R.color.bluePrimary)) + .setSolidColor(getResources().getColor(R.color.bluePrimary)) + .setCornersRadius(ScreenUtil.dp2px(30)) + .build(); + imageView.setBackground(drawable); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getApplicationContext(), TTSActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + + + FloatWindow + .with(getApplicationContext()) + .setView(imageView) + .setWidth(Screen.width, 0.15f) //设置悬浮控件宽高 + .setHeight(Screen.width, 0.15f) + .setX(Screen.width, 0.8f) //设置控件初始位置 + .setY(Screen.height, 0.8f) + .setMoveType(MoveType.slide, 10, 10, 10, 10) + .setMoveStyle(500, new BounceInterpolator()) + .setFilter(true, MainActivity.class, ArticleActivity.class) + .setDesktopShow(false) + .build(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if( item.getItemId() == android.R.id.home ){ + this.finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + } + return super.onOptionsItemSelected(item); + } + + @Override + protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + return mColorfulBuilder; + } + +} diff --git a/app/src/main/java/me/wizos/loread/activity/WebActivity.java b/app/src/main/java/me/wizos/loread/activity/WebActivity.java index 2bdcf40..980036c 100644 --- a/app/src/main/java/me/wizos/loread/activity/WebActivity.java +++ b/app/src/main/java/me/wizos/loread/activity/WebActivity.java @@ -8,69 +8,92 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.net.Uri; +import android.net.http.SslError; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.support.v7.widget.Toolbar; +import android.os.Handler; import android.text.InputType; import android.text.TextUtils; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; -import android.view.WindowManager; -import android.webkit.JavascriptInterface; -import android.webkit.WebChromeClient; +import android.view.ViewConfiguration; +import android.webkit.CookieManager; +import android.webkit.SslErrorHandler; 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 androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; +import com.google.android.material.appbar.AppBarLayout; +import com.hjq.toast.ToastUtils; import com.just.agentweb.AgentWeb; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.NestedScrollAgentWebView; +import com.just.agentweb.WebChromeClient; +import com.just.agentweb.WebViewClient; import com.socks.library.KLog; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Map; +import me.wizos.loread.App; +import me.wizos.loread.Contract; import me.wizos.loread.R; -import me.wizos.loread.bean.config.GlobalConfig; -import me.wizos.loread.bean.config.UserAgent; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.utils.InjectUtil; -import me.wizos.loread.utils.ToastUtil; -import me.wizos.loread.utils.Tool; +import me.wizos.loread.bridge.WebBridge; +import me.wizos.loread.config.AdBlock; +import me.wizos.loread.config.LinkRewriteConfig; +import me.wizos.loread.config.NetworkUserAgentConfig; +import me.wizos.loread.utils.ScreenUtil; +import me.wizos.loread.utils.VideoInjectUtil; import me.wizos.loread.view.colorful.Colorful; -import me.wizos.loread.view.webview.AdBlock; import me.wizos.loread.view.webview.DownloadListenerS; +import me.wizos.loread.view.webview.LongClickPopWindow; + +import static me.wizos.loread.Contract.HTTP; +import static me.wizos.loread.Contract.HTTPS; +import static me.wizos.loread.Contract.SCHEMA_HTTP; +import static me.wizos.loread.Contract.SCHEMA_HTTPS; +import static me.wizos.loread.Contract.SCHEMA_LOREAD; /** + * 内置的 webView 页面,用来相应 a,iframe 的跳转内容 * @author Wizos */ -// 内置的webview页面,用来相应 a,iframe 的跳转内容 -public class WebActivity extends BaseActivity { - public static final String TAG = BaseActivity.class.getSimpleName(); - +public class WebActivity extends BaseActivity implements WebBridge { private AgentWeb agentWeb; private Toolbar mToolbar; + private AppBarLayout appBarLayout; private CoordinatorLayout containerLayout; + private String originalUrl; + private String receivedUrl; + private static Handler handler = new Handler(); + private int downX, downY; + private boolean isPortrait = true; //是否为竖屏 + @Override protected void onCreate(Bundle savedInstanceState) { - // 获取默认主题 - WithPref.i().setThemeMode(getIntent().getIntExtra("theme", 0)); super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); containerLayout = this.findViewById(R.id.web_root); + appBarLayout = this.findViewById(R.id.web_appBarLayout); mToolbar = this.findViewById(R.id.web_toolbar); - - - mToolbar.setTitle(getIntent().getStringExtra("title") + ""); + mToolbar.setTitle(getString(R.string.loading)); setSupportActionBar(mToolbar); // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 getSupportActionBar().setHomeButtonEnabled(true); @@ -78,17 +101,39 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(true); + if (savedInstanceState != null) { + onRecoveryInstanceState(savedInstanceState); + } - String link = getIntent().getDataString(); - // 补救,获取 link - if (TextUtils.isEmpty(link)) { - link = getIntent().getStringExtra(Intent.EXTRA_TEXT); + originalUrl = getIntent().getDataString(); + // 补救,获取 originalUrl + if (TextUtils.isEmpty(originalUrl)) { + originalUrl = getIntent().getStringExtra(Intent.EXTRA_TEXT); } - mToolbar.setSubtitle(link); - KLog.e("获取到链接,准备跳转" + link); - initWebView(link); + + + String newUrl = LinkRewriteConfig.i().getRedirectUrl(originalUrl); + KLog.i("获取到链接,准备跳转B:" + originalUrl + ", newUrl = " + newUrl); + if (!TextUtils.isEmpty(newUrl)) { + originalUrl = newUrl; + } + + mToolbar.setSubtitle(originalUrl); + + initWebView(originalUrl); + + mToolbar.setOnClickListener(view -> { + if (handler.hasMessages(App.MSG_DOUBLE_TAP) && agentWeb != null) { + handler.removeMessages(App.MSG_DOUBLE_TAP); + agentWeb.getWebCreator().getWebView().scrollTo(0, 0); + } else { + handler.sendEmptyMessageDelayed(App.MSG_DOUBLE_TAP, ViewConfiguration.getDoubleTapTimeout()); + } + }); } + + @SuppressLint({"ClickableViewAccessibility", "SetJavaScriptEnabled"}) private void initWebView(String link) { CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(-1, -1); lp.setBehavior(new AppBarLayout.ScrollingViewBehavior()); @@ -98,22 +143,11 @@ private void initWebView(String link) { // .setAgentWebParent( containerLayout, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))//传入AgentWeb的父控件。 .useDefaultIndicator(-1, 3)//设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 .setWebView(new NestedScrollAgentWebView(this)) - .setWebViewClient(mWebViewClient)//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 + .setWebViewClient(mWebViewClient)//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(webViewScroll)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 .setWebChromeClient(mWebChromeClient) //WebChromeClient .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.ASK)//打开其他应用时,弹窗咨询用户是否前往其他应用 - .addJavascriptInterface("VideoBridge", new Object() { - @JavascriptInterface - public void toggleScreenOrientation() { - WebActivity.this.toggleScreenOrientation(); - } - - @JavascriptInterface - public void log(String paramString) { - KLog.e("VideoBridge", paramString); - } - }) -// .interceptUnkownUrl() //拦截找不到相关页面的Scheme + .addJavascriptInterface(WebBridge.TAG, WebActivity.this) // .setAgentWebWebSettings(getSettings())//设置 IAgentWebSettings。 // .setPermissionInterceptor(mPermissionInterceptor) //权限拦截 2.0.0 加入。 // .setAgentWebUIController(new UIController(getActivity())) //自定义UI AgentWeb3.0.0 加入。 @@ -125,7 +159,59 @@ public void log(String paramString) { .interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 .createAgentWeb()//创建AgentWeb。 .ready()//设置 WebSettings。 - .loadUrl(link); //WebView载入该url地址的页面并显示。 + .go(link); //WebView载入该url地址的页面并显示。 +// agentWeb.getWebCreator().getWebView().setHorizontalScrollBarEnabled(false); + agentWeb.getWebCreator().getWebView().getSettings().setTextZoom(100); + // 设置最小的字号,默认为8 + agentWeb.getWebCreator().getWebView().getSettings().setMinimumFontSize(10); + // 设置最小的本地字号,默认为8 + agentWeb.getWebCreator().getWebView().getSettings().setMinimumLogicalFontSize(10); + + // 设置此属性,可任意比例缩放 + agentWeb.getWebCreator().getWebView().getSettings().setUseWideViewPort(true); + // 缩放至屏幕的大小:如果webview内容宽度大于显示区域的宽度,那么将内容缩小,以适应显示区域的宽度, 默认是false + agentWeb.getWebCreator().getWebView().getSettings().setLoadWithOverviewMode(true); + // NARROW_COLUMNS 适应内容大小 , SINGLE_COLUMN 自适应屏幕 + agentWeb.getWebCreator().getWebView().getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); + + //缩放操作 + agentWeb.getWebCreator().getWebView().getSettings().setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。 + agentWeb.getWebCreator().getWebView().getSettings().setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放 + agentWeb.getWebCreator().getWebView().getSettings().setDisplayZoomControls(false); //隐藏原生的缩放控件 + + agentWeb.getWebCreator().getWebView().getSettings().setDefaultTextEncodingName(StandardCharsets.UTF_8.name()); + + agentWeb.getWebCreator().getWebView().getSettings().setJavaScriptEnabled(true); + // 支持通过js打开新的窗口 + agentWeb.getWebCreator().getWebView().getSettings().setJavaScriptCanOpenWindowsAutomatically(false); + + /* 缓存 */ + agentWeb.getWebCreator().getWebView().getSettings().setDomStorageEnabled(true); // 临时简单的缓存(必须保留,否则无法播放优酷视频网页,其他的可以) + agentWeb.getWebCreator().getWebView().getSettings().setAppCacheEnabled(true); // 支持H5的 application cache 的功能 + // webSettings.setDatabaseEnabled(true); // 支持javascript读写db + //根据cache-control获取数据。 + agentWeb.getWebCreator().getWebView().getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); + + // 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭 + agentWeb.getWebCreator().getWebView().getSettings().setAllowFileAccessFromFileURLs(false); + // 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源 + agentWeb.getWebCreator().getWebView().getSettings().setAllowUniversalAccessFromFileURLs(false); + // 允许访问文件 + agentWeb.getWebCreator().getWebView().getSettings().setAllowFileAccess(true); + + // 保存表单数据 + agentWeb.getWebCreator().getWebView().getSettings().setSaveFormData(true); + agentWeb.getWebCreator().getWebView().getSettings().setSavePassword(true); + + // 允许在Android 5.0上 Webview 加载 Http 与 Https 混合内容。作者:Wing_Li,链接:https://www.jianshu.com/p/3fcf8ba18d7f + agentWeb.getWebCreator().getWebView().getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + + CookieManager instance = CookieManager.getInstance(); + instance.setAcceptCookie(true); + instance.setAcceptThirdPartyCookies(agentWeb.getWebCreator().getWebView(), true); + CookieManager.setAcceptFileSchemeCookies(true); + + agentWeb.getWebCreator().getWebView().getSettings().setMediaPlaybackRequiresUserGesture(true); /** * https://www.jianshu.com/p/6e38e1ef203a @@ -135,133 +221,269 @@ public void log(String paramString) { new DownloadListenerS(WebActivity.this).setWebView(agentWeb.getWebCreator().getWebView()) ); - if (GlobalConfig.i().getUserAgentIndex() == -2) { - String guessUserAgent = GlobalConfig.i().guessUserAgentByUrl(link); - if (!TextUtils.isEmpty(guessUserAgent)) { - agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(guessUserAgent); + String guessUserAgent = NetworkUserAgentConfig.i().guessUserAgentByUrl(link); + if (!TextUtils.isEmpty(guessUserAgent)) { + agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(guessUserAgent); + } + + agentWeb.getWebCreator().getWebView().setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View arg0, MotionEvent arg1) { + downX = (int) arg1.getX(); + downY = (int) arg1.getY(); + return false; } - } else if (GlobalConfig.i().getUserAgentIndex() != -1) { - agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(GlobalConfig.i().getUserAgentString()); + }); + agentWeb.getWebCreator().getWebView().setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View webView) { + final WebView.HitTestResult result = ((WebView) webView).getHitTestResult(); + if (null == result) { + return false; + } + int type = result.getType(); + if (type == WebView.HitTestResult.UNKNOWN_TYPE) { + return false; + } + +// // 这里可以拦截很多类型,我们只处理超链接就可以了 +// final LongClickPopWindow webViewLongClickedPopWindow = +// new LongClickPopWindow(WebActivity.this, status, ScreenUtil.dp2px(WebActivity.this,120), ScreenUtil.dp2px(WebActivity.this,90)); +// webViewLongClickedPopWindow.showAtLocation(webView, Gravity.TOP|Gravity.LEFT, downX, downY + 10); + new LongClickPopWindow(WebActivity.this, (WebView) webView, ScreenUtil.dp2px(WebActivity.this, 120), ScreenUtil.dp2px(WebActivity.this, 130), downX, downY + 10); + + return true; + } + }); + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // 后者为短期内按下的次数 + if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { + if (agentWeb.getWebCreator().getWebView().canGoBack()) { + if (WebActivity.this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + portrait(); + } + agentWeb.getWebCreator().getWebView().stopLoading(); + agentWeb.back(); + } else { + exit(); + } + return true; } - agentWeb.getWebCreator().getWebView().getSettings().setUseWideViewPort(true); - agentWeb.getWebCreator().getWebView().getSettings().setSupportZoom(true); - agentWeb.getWebCreator().getWebView().getSettings().setTextZoom(100); - agentWeb.getWebCreator().getWebView().getSettings().setBuiltInZoomControls(true); - agentWeb.getWebCreator().getWebView().getSettings().setLoadWithOverviewMode(true); - agentWeb.getWebCreator().getWebView().getSettings().setSavePassword(true); - agentWeb.getWebCreator().getWebView().getSettings().setSaveFormData(true);// 保存表单数据 + return super.onKeyDown(keyCode, event); + } + - Tool.setBackgroundColor(agentWeb.getWebCreator().getWebView()); + private static final int UI_ANIMATION_DELAY = 300; + private final Runnable mHidePart2Runnable = new Runnable() { + @SuppressLint("InlinedApi") + @Override + public void run() { + KLog.e("屏幕:隐藏状态"); + + AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) appBarLayout.getChildAt(0).getLayoutParams(); + KLog.e("屏幕:隐藏状态" + containerLayout.getSystemUiVisibility()); + params.setScrollFlags(0); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + +// View.SYSTEM_UI_FLAG_LOW_PROFILE 状态栏显示处于低能显示状态(low profile模式),状态栏上一些图标显示会被隐藏。 +// View.SYSTEM_UI_FLAG_FULLSCREEN 隐藏状态栏:全屏显示,但状态栏不会被隐藏覆盖,状态栏依然可见,Activity顶端布局部分会被状态遮住。 +// View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 隐藏导航栏 +// View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 布局占用状态栏区域 +// View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 布局占用导航栏区域 +// View.SYSTEM_UI_FLAG_LAYOUT_STABLE 稳定布局,防止系统栏隐藏时内容区域大小发生变化 +// View.SYSTEM_UI_FLAG_IMMERSIVE 沉浸式 +// View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 粘性沉浸式:向内滑动的操作会让系统栏临时显示 + containerLayout.setFitsSystemWindows(false); + containerLayout.setSystemUiVisibility( +// | View.SYSTEM_UI_FLAG_IMMERSIVE + View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_FULLSCREEN // landscape status bar + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // landscape nav bar + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + ); + } + }; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // Delayed display of UI elements + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) appBarLayout.getChildAt(0).getLayoutParams(); + params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); + containerLayout.setFitsSystemWindows(true); + containerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + }; + + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putBoolean(Contract.isPortrait, isPortrait); + KLog.i("自动保存,是否为竖屏:" + isPortrait); + super.onSaveInstanceState(outState); } -// private WebViewS webViewS = new WebViewS(App.i()); -// private void initWebView2( String link ){ -//// WebViewS webViewS = new WebViewS(App.i()); -// containerLayout.addView(webViewS); -// -// webViewS.setWebViewClient( mWebViewClient ); -// webViewS.setWebChromeClient(mWebChromeClient ); -// webViewS.addJavascriptInterface( new Object(){ -// @JavascriptInterface -// public void toggleScreenOrientation(){ -// WebActivity.this.toggleScreenOrientation(); -// } -// @JavascriptInterface -// public void log(String paramString) { -// KLog.e("VideoBridge", paramString); -// } -// } , "VideoBridge"); -// -// webViewS.setDownloadListener( -// new DownloadListenerS(WebActivity.this).setWebView(webViewS) -// ); -// -// -// if( GlobalConfig.i().getUserAgentIndex() == -2 ){ -// String guessUserAgent = GlobalConfig.i().guessUserAgentByUrl(link); -// if( !TextUtils.isEmpty(guessUserAgent) ){ -// webViewS.getSettings().setUserAgentString( guessUserAgent ); -// } -// }else if( GlobalConfig.i().getUserAgentIndex()!= -1 ){ -// webViewS.getSettings().setUserAgentString( GlobalConfig.i().getUserAgentString() ); -// } -// Tool.setBackgroundColor(webViewS); -// webViewS.loadUrl(link); -// } + private void onRecoveryInstanceState(@NonNull Bundle outState) { + if (outState.getBoolean(Contract.isPortrait, true)) { + portrait(); + } else { + landscape(); + } + } + /** + * 切换为横屏 + */ + @SuppressLint("SourceLockedOrientationActivity") + private void landscape() { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + isPortrait = false; + // Schedule a runnable to remove the status and navigation bar after a delay + handler.removeCallbacks(mShowPart2Runnable); + handler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + } - private void toggleScreenOrientation() { - // 横排PORTRAIT, LANDSCAPE 竖排 - if (WebActivity.this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - WebActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + /** + * 切换为竖屏 + */ + @SuppressLint("SourceLockedOrientationActivity") + private void portrait() { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + isPortrait = true; + // Schedule a runnable to display UI elements after a delay + handler.removeCallbacks(mHidePart2Runnable); + handler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); + } + + + @Override + public void log(String msg) { + KLog.e(WebBridge.TAG, msg); + } + + @Override + public void toggleScreenOrientation() { + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + landscape(); } else { - WebActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + portrait(); } } + protected WebChromeClient mWebChromeClient = new WebChromeClient() { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); if (!TextUtils.isEmpty(title)) { mToolbar.setTitle(title); - mToolbar.setSubtitle(agentWeb.getWebCreator().getWebView().getUrl()); -// mToolbar.setSubtitle( webViewS.getUrl()); + receivedUrl = agentWeb.getWebCreator().getWebView().getUrl(); + mToolbar.setSubtitle(receivedUrl); } } }; protected WebViewClient mWebViewClient = new WebViewClient() { - @Deprecated - @SuppressLint("NewApi") @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - if (GlobalConfig.i().isBlockAD() && AdBlock.i().isAd(url)) { - // 有广告的请求数据,我们直接返回空数据,注:不能直接返回null - return new WebResourceResponse(null, null, null); - } - return super.shouldInterceptRequest(view, (url)); - } + public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) { + String scheme = request.getUrl().getScheme().trim(); + if (scheme.equalsIgnoreCase(HTTP) || scheme.equalsIgnoreCase(HTTPS)) { + String url = request.getUrl().toString(); + if (AdBlock.i().isAd(url)) { + // 有广告的请求数据,我们直接返回空数据,注:不能直接返回null + return new WebResourceResponse(null, null, null); + } - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - return shouldInterceptRequest(view, request.getUrl() + ""); + String newUrl = LinkRewriteConfig.i().getRedirectUrl(url); + // KLog.e("重定向地址:" + url + " , " + newUrl); + if(!TextUtils.isEmpty(newUrl) && !url.equalsIgnoreCase(newUrl)){ + return super.shouldInterceptRequest(view, new WebResourceRequest() { + @Override + public Uri getUrl() { + return Uri.parse(newUrl); + } + @SuppressLint("NewApi") + @Override + public boolean isRedirect(){ + return true; + } + @SuppressLint("NewApi") + @Override + public boolean isForMainFrame() { + return request.isForMainFrame(); + } + @SuppressLint("NewApi") + @Override + public boolean hasGesture() { + return request.hasGesture(); + } + @SuppressLint("NewApi") + @Override + public String getMethod() { + return request.getMethod(); + } + @SuppressLint("NewApi") + @Override + public Map getRequestHeaders() { + return request.getRequestHeaders(); + } + }); + } + } + return super.shouldInterceptRequest(view, request); } @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return shouldOverrideUrlLoading(view, request.getUrl() + ""); - } - @Override - public boolean shouldOverrideUrlLoading(final WebView view, String url) { + public boolean shouldOverrideUrlLoading(final WebView view, WebResourceRequest request) { // 优酷想唤起自己应用播放该视频,下面拦截地址返回true则会在应用内 H5 播放,禁止优酷唤起播放该视频。 // 如果返回false , DefaultWebClient 会根据intent协议处理该地址,首先匹配该应用存不存在, - // 如果存在,唤起该应用播放,如果不存在,则跳到应用市场下载该应用 . - if (url.startsWith("http") || url.startsWith("https")) { + // 如果存在,唤起该应用播放,如果不存在,则跳到应用市场下载该应用. + String url = request.getUrl().toString(); + + KLog.i("地址:" + url); + if (url.startsWith(SCHEMA_HTTP) || url.startsWith(SCHEMA_HTTPS)) { return false; } //其他的URL则会开启一个Acitity然后去调用原生APP final Intent in = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - if (in.resolveActivity(getPackageManager()) != null) { + if (url.startsWith(SCHEMA_LOREAD)) { + startActivity(in); + finish(); + }else if (in.resolveActivity(getPackageManager()) != null) { in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - String name = "相应的"; + String name = null; try { name = "" + getPackageManager().getApplicationLabel(getPackageManager().getApplicationInfo(in.resolveActivity(getPackageManager()).getPackageName(), PackageManager.GET_META_DATA)); } catch (PackageManager.NameNotFoundException e) { + KLog.e(R.string.unable_to_find_app); e.printStackTrace(); } + if (TextUtils.isEmpty(name)) { + name = getString(R.string.corresponding); + } new MaterialDialog.Builder(WebActivity.this) - .content("是否跳转到「" + name + "」应用?") - .negativeText("取消") + .content(R.string.do_you_want_to_jump_the_application, name) + .negativeText(R.string.disagree) .positiveText(R.string.agree) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { startActivity(in); + finish(); } }) .show(); @@ -269,108 +491,137 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) return true; } + @Override + public void onPageStarted(WebView webView, String url, Bitmap favicon) { + KLog.i("onPageStarted = " + url); + super.onPageStarted(webView, url, favicon); + + if (itemRefresh != null) { + itemRefresh.setVisible(false); + } + if (itemStop != null) { + itemStop.setVisible(true); + } + } + @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 注入视频全屏js - view.loadUrl(InjectUtil.fullScreenJsFun(url)); + view.loadUrl(VideoInjectUtil.fullScreenJsFun(url)); + if (itemStop != null) { + itemStop.setVisible(false); + } + if (itemRefresh != null) { + itemRefresh.setVisible(true); + } } @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); + //KLog.e("接受http错误 = " + request.getUrl() + errorResponse); + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + //KLog.e("接受Ssl错误 = " + view.getUrl() + error); + handler.proceed();//接受证书 } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { super.onReceivedError(view, request, error); + //KLog.e("接受错误 = " + request.getUrl() + error); } }; + MenuItem itemRefresh, itemStop; + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_web, menu); + itemRefresh = menu.findItem(R.id.web_menu_refresh); + itemStop = menu.findItem(R.id.web_menu_stop); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + //监听左上角的返回箭头 + case android.R.id.home: + exit(); + break; case R.id.web_menu_user_agent: final ArrayList uaTitle = new ArrayList<>(); - uaTitle.add("默认"); - for (UserAgent userAgent : GlobalConfig.i().getUserAgents()) { - uaTitle.add(userAgent.getName()); - KLog.e("标题:" + userAgent.getName()); + String holdUA = NetworkUserAgentConfig.i().getHoldUserAgent(); + uaTitle.add(getString(R.string.default_x)); + int i = 0; + int selected = 0; + for (Map.Entry entry : NetworkUserAgentConfig.i().getUserAgents().entrySet()) { + uaTitle.add(entry.getKey()); + if(entry.getKey().equals(holdUA)){ + selected = i; + } + i++; + KLog.e("标题:" + entry.getKey()); } - final int index = GlobalConfig.i().getUserAgentIndex(); -// KLog.e("菜单被点击1" + App.i().globalConfig ); - KLog.e("当前选择的是:" + index); + int finalSelected = selected; new MaterialDialog.Builder(WebActivity.this) - .title("选择UA标识") + .title(R.string.select_user_agent) .items(uaTitle) - .itemsCallbackSingleChoice(index + 1, new MaterialDialog.ListCallbackSingleChoice() { + .itemsCallbackSingleChoice(selected + 1, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, final int which, CharSequence text) { // 默认 if (which == 0) { - GlobalConfig.i().setUserAgentIndex(which - 1); - GlobalConfig.i().save(); -// webViewS.getSettings().setUserAgentString( null ); -// webViewS.reload(); - agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(null); - agentWeb.getWebCreator().getWebView().reload(); -// KLog.e("默认的UA是:" + agentWeb.getWebCreator().getWebView().getSettings().getUserAgentString() ); + NetworkUserAgentConfig.i().setHoldUserAgent(null); } // 手动选择项 - else if (which - 1 != index) { - GlobalConfig.i().setUserAgentIndex(which - 1); - GlobalConfig.i().save(); -// webViewS.getSettings().setUserAgentString( GlobalConfig.i().getUserAgentString() ); -// webViewS.reload(); - agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(GlobalConfig.i().getUserAgentString()); - agentWeb.getWebCreator().getWebView().reload(); + else if (which - 1 != finalSelected) { + NetworkUserAgentConfig.i().setHoldUserAgent(text.toString()); + } + NetworkUserAgentConfig.i().save(); + agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(NetworkUserAgentConfig.i().guessUserAgentByUrl(receivedUrl)); + agentWeb.getWebCreator().getWebView().reload(); +// KLog.e("默认的UA是:" + agentWeb.getWebCreator().getWebView().getSettings().getUserAgentString() ); dialog.dismiss(); return true; } }) - .neutralText("自定义UA") + .neutralText(R.string.custom_user_agent) .onNeutral(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { new MaterialDialog.Builder(WebActivity.this) - .title("输入UA标识") + .title(R.string.enter_user_agent) .inputType(InputType.TYPE_CLASS_TEXT) .inputRange(12, 200) - .input(null, GlobalConfig.i().getUserAgentString(), new MaterialDialog.InputCallback() { + .input(null, agentWeb.getWebCreator().getWebView().getSettings().getUserAgentString(), new MaterialDialog.InputCallback() { @Override - public void onInput(MaterialDialog dialog, CharSequence input) { - GlobalConfig.i().setUserAgentIndex(GlobalConfig.i().getUserAgents().size()); - GlobalConfig.i().getUserAgents().add(new UserAgent("自定义", input.toString())); - GlobalConfig.i().save(); + public void onInput(@NotNull MaterialDialog dialog, CharSequence input) { + NetworkUserAgentConfig.i().setHoldUserAgent(getString(R.string.custom)); + NetworkUserAgentConfig.i().getUserAgents().put(getString(R.string.custom),input.toString()); + NetworkUserAgentConfig.i().save(); KLog.e("当前输入的是:" + input.toString()); -// webViewS.getSettings().setUserAgentString( GlobalConfig.i().getUserAgentString() ); -// webViewS.reload(); - agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(GlobalConfig.i().getUserAgentString()); + agentWeb.getWebCreator().getWebView().getSettings().setUserAgentString(NetworkUserAgentConfig.i().guessUserAgentByUrl(receivedUrl)); agentWeb.getWebCreator().getWebView().reload(); } }) .positiveText(R.string.confirm) .negativeText(android.R.string.cancel) - .neutralText("删除自定义UA") + .neutralText(R.string.remove_custom_user_agent) .neutralColor(WebActivity.this.getResources().getColor(R.color.material_red_400)) .onNeutral(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - if (GlobalConfig.i().getUserAgents().size() == 4) { - GlobalConfig.i().getUserAgents().remove(3); - } - if (GlobalConfig.i().getUserAgentIndex() == 3) { - GlobalConfig.i().setUserAgentIndex(-1); - } + NetworkUserAgentConfig.i().setHoldUserAgent(getString(R.string.custom)); + NetworkUserAgentConfig.i().getUserAgents().remove(getString(R.string.custom)); + NetworkUserAgentConfig.i().save(); } }) .show(); @@ -378,41 +629,48 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) }) .show(); break; - //监听左上角的返回箭头 - case android.R.id.home: - exit(); - break; case R.id.web_menu_open_by_sys: - Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setData(Uri.parse(agentWeb.getWebCreator().getWebView().getUrl())); -// intent.setData(Uri.parse(webViewS.getUrl())); + Intent intent = new Intent(Intent.ACTION_VIEW); + if (!TextUtils.isEmpty(receivedUrl)) { + intent.setData(Uri.parse(receivedUrl)); + } else { + intent.setData(Uri.parse(originalUrl)); + } startActivity(intent); break; case R.id.web_menu_copy_link: //获取剪贴板管理器: ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - // 创建普通字符型ClipData - ClipData mClipData = ClipData.newPlainText("url", agentWeb.getWebCreator().getWebView().getUrl()); + ClipData mClipData; + if (!TextUtils.isEmpty(receivedUrl)) { + // 创建普通字符型ClipData + mClipData = ClipData.newRawUri(agentWeb.getWebCreator().getWebView().getTitle(), Uri.parse(receivedUrl)); + } else { + // 创建普通字符型ClipData + mClipData = ClipData.newRawUri(agentWeb.getWebCreator().getWebView().getTitle(), Uri.parse(originalUrl)); + } // ClipData mClipData = ClipData.newPlainText("url",webViewS.getUrl()); // 将ClipData内容放到系统剪贴板里。 cm.setPrimaryClip(mClipData); - ToastUtil.showLong("复制成功"); + ToastUtils.show(getString(R.string.copy_success)); break; case R.id.web_menu_share: Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, mToolbar.getTitle()); // sendIntent.putExtra(Intent.EXTRA_TEXT, mToolbar.getTitle() + " " + webViewS.getUrl() ); sendIntent.putExtra(Intent.EXTRA_TEXT, mToolbar.getTitle() + " " + agentWeb.getWebCreator().getWebView().getUrl()); sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(Intent.createChooser(sendIntent, "分享到")); -// overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_to))); + overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); break; case R.id.web_menu_refresh: -// webViewS.reload(); agentWeb.getWebCreator().getWebView().reload(); break; + case R.id.web_menu_stop: + agentWeb.getWebCreator().getWebView().stopLoading(); + break; default: break; } @@ -429,46 +687,27 @@ public void onConfigurationChanged(Configuration newConfig) { } - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // 后者为短期内按下的次数 - if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { - if (agentWeb.getWebCreator().getWebView().canGoBack()) { - agentWeb.back(); - -// if( webViewS.canGoBack()){ -// webViewS.goBack(); - } else { - exit(); - } - return true; - } - return super.onKeyDown(keyCode, event); - } - private void exit() { this.finish(); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); -// System.exit(0); } @Override protected void onPause() { super.onPause(); agentWeb.getWebLifeCycle().onPause(); -// webViewS.onPause(); } @Override protected void onResume() { super.onResume(); agentWeb.getWebLifeCycle().onResume(); -// webViewS.onResume(); } @Override protected void onDestroy() { super.onDestroy(); + CookieManager.getInstance().flush(); agentWeb.getWebCreator().getWebView().stopLoading(); agentWeb.getWebCreator().getWebView().clearCache(true); agentWeb.getWebCreator().getWebView().clearHistory(); @@ -476,7 +715,6 @@ protected void onDestroy() { agentWeb.getWebCreator().getWebView().removeAllViews(); agentWeb.getWebCreator().getWebParentLayout().removeAllViews(); agentWeb.getWebLifeCycle().onDestroy(); -// webViewS.destroy(); } @Override @@ -495,7 +733,6 @@ protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。 - #####二、仿魅族应用商店应用详情效果 作为一个多年的魅族手机使用者,看起来魅族的应用商店也挺不错的,来看看要实现的效果(**注:实现效果而非实现实现界面**) @@ -599,7 +836,7 @@ protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { // public void onClick(View view) { // if (!mBottomSheetDialog.isShowing()) { // containerBehaviour.setPeekHeight((int) (0.9 * height)); -// mBottomSheetDialog.show(); +// mBottomSheetDialog.portrait(); // } else { // mBottomSheetDialog.dismiss(); // } diff --git a/app/src/main/java/me/wizos/loread/activity/login/LoginFormState.java b/app/src/main/java/me/wizos/loread/activity/login/LoginFormState.java new file mode 100644 index 0000000..180d8f6 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/login/LoginFormState.java @@ -0,0 +1,66 @@ +package me.wizos.loread.activity.login; + +import androidx.annotation.Nullable; + +/** + * Data validation state of the login form. + */ +public class LoginFormState { + @Nullable + private Integer hostHint; + @Nullable + private Integer usernameHint; + @Nullable + private Integer passwordHint; + private boolean isDataValid; + + public LoginFormState() { } + + public LoginFormState(@Nullable Integer usernameHint, @Nullable Integer passwordHint) { + this.usernameHint = usernameHint; + this.passwordHint = passwordHint; + this.isDataValid = false; + } + + public LoginFormState(boolean isDataValid) { + this.hostHint = null; + this.usernameHint = null; + this.passwordHint = null; + this.isDataValid = isDataValid; + } + + + public void setHostHint(@Nullable Integer hostHint) { + this.hostHint = hostHint; + } + + public void setUsernameHint(@Nullable Integer usernameHint) { + this.usernameHint = usernameHint; + } + + public void setPasswordHint(@Nullable Integer passwordHint) { + this.passwordHint = passwordHint; + } + + @Nullable + Integer getHostHint() { + return hostHint; + } + @Nullable + Integer getUsernameHint() { + return usernameHint; + } + + @Nullable + Integer getPasswordHint() { + return passwordHint; + } + + public void setDataValid(boolean dataValid) { + isDataValid = dataValid; + } + + boolean isDataValid() { + return isDataValid; + } +} diff --git a/app/src/main/java/me/wizos/loread/activity/login/LoginInoReaderActivity.java b/app/src/main/java/me/wizos/loread/activity/login/LoginInoReaderActivity.java new file mode 100644 index 0000000..d7a9871 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/login/LoginInoReaderActivity.java @@ -0,0 +1,151 @@ +package me.wizos.loread.activity.login; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.hjq.toast.ToastUtils; +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.activity.BaseActivity; +import me.wizos.loread.view.colorful.Colorful; +import me.wizos.loread.viewmodel.InoReaderUserViewModel; + +public class LoginInoReaderActivity extends BaseActivity { + private InoReaderUserViewModel loginViewModel; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login_inoreader); + Toolbar mToolbar = findViewById(R.id.inoreader_toolbar); + setSupportActionBar(mToolbar); + // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 + getSupportActionBar().setHomeButtonEnabled(true); + // 决定左上角图标的左侧是否有向左的小箭头,true 有小箭头 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); + + loginViewModel = new ViewModelProvider(this).get(InoReaderUserViewModel.class); + + final EditText hostEditText = findViewById(R.id.inoreader_host_edittext); + final EditText usernameEditText = findViewById(R.id.inoreader_username_edittext); + final EditText passwordEditText = findViewById(R.id.inoreader_password_edittext); + final Button loginButton = findViewById(R.id.inoreader_login_button); + final ProgressBar loadingProgressBar = findViewById(R.id.inoreader_loading); + + + TextWatcher afterTextChangedListener = 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) { + } + + @Override + public void afterTextChanged(Editable s) { + loginViewModel.loginDataChanged( + hostEditText.getText().toString(), + usernameEditText.getText().toString(), + passwordEditText.getText().toString() + ); + } + }; + hostEditText.addTextChangedListener(afterTextChangedListener); + usernameEditText.addTextChangedListener(afterTextChangedListener); + passwordEditText.addTextChangedListener(afterTextChangedListener); + + /** + * 需要注意的是 setOnEditorActionListener这个方法,并不是在我们点击EditText的时候触发, + * 也不是在我们对EditText进行编辑时触发,而是在我们编辑完之后点击软键盘上的各种键才会触发。 + * 因为通过布局文件中的imeOptions可以控制软件盘右下角的按钮显示为不同按钮。所以和EditorInfo搭配起来可以实现各种软键盘的功能。 + */ + passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + loadingProgressBar.setVisibility(View.VISIBLE); + loginViewModel.login( + hostEditText.getText().toString(), + usernameEditText.getText().toString(), + passwordEditText.getText().toString() + ); + } + return false; + } + }); + + loginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + loadingProgressBar.setVisibility(View.VISIBLE); + loginButton.setEnabled(false); + loginViewModel.login( + hostEditText.getText().toString(), + usernameEditText.getText().toString(), + passwordEditText.getText().toString() + ); + } + }); + + loginViewModel.getLoginFormLiveData().observe(this, new Observer() { + @Override + public void onChanged(@Nullable LoginFormState loginFormState) { + if (loginFormState == null) { + return; + } + loginButton.setEnabled(loginFormState.isDataValid()); + + if (loginFormState.getHostHint() != null) { + hostEditText.setError(getString(loginFormState.getHostHint())); + } + if (loginFormState.getUsernameHint() != null) { + usernameEditText.setError(getString(loginFormState.getUsernameHint())); + } + if (loginFormState.getPasswordHint() != null) { + passwordEditText.setError(getString(loginFormState.getPasswordHint())); + } + } + }); + + loginViewModel.getLoginResult().observe(this, new Observer() { + @Override + public void onChanged(@Nullable LoginResult loginResult) { + if (loginResult == null) { + return; + } + loginButton.setEnabled(true); + loadingProgressBar.setVisibility(View.GONE); + String tips = getString(R.string.welcome); + if (loginResult.isSuccess()) { + ToastUtils.show(tips); + KLog.e( tips + loginResult.getData() ); + setResult(App.ActivityResult_LoginPageToProvider); + finish(); + overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); + } else { + ToastUtils.show(loginResult.getData()); + } + } + }); + } + public Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + return mColorfulBuilder; + } +} diff --git a/app/src/main/java/me/wizos/loread/activity/login/LoginResult.java b/app/src/main/java/me/wizos/loread/activity/login/LoginResult.java new file mode 100644 index 0000000..e5a835c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/login/LoginResult.java @@ -0,0 +1,45 @@ +package me.wizos.loread.activity.login; + +import androidx.annotation.Nullable; + +/** + * Authentication result : success (user details) or error message. + */ +public class LoginResult { + @Nullable + private boolean success; + @Nullable + private String data; + + public LoginResult() { + } + + @Nullable + public LoginResult setSuccess(boolean success) { + this.success = success; + return this; + } + + @Nullable + public boolean isSuccess() { + return success; + } + + @Nullable + public String getData() { + return data; + } + + public LoginResult setData(@Nullable String data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return "LoginResult{" + + "success=" + success + + ", data='" + data + '\'' + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/activity/login/LoginTinyRSSActivity.java b/app/src/main/java/me/wizos/loread/activity/login/LoginTinyRSSActivity.java new file mode 100644 index 0000000..1e2f502 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/activity/login/LoginTinyRSSActivity.java @@ -0,0 +1,151 @@ +package me.wizos.loread.activity.login; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.hjq.toast.ToastUtils; +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.activity.BaseActivity; +import me.wizos.loread.view.colorful.Colorful; +import me.wizos.loread.viewmodel.TinyRSSUserViewModel; + +public class LoginTinyRSSActivity extends BaseActivity { + private TinyRSSUserViewModel loginViewModel; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login_tiny_rss); + Toolbar mToolbar = findViewById(R.id.tiny_rss_toolbar); + setSupportActionBar(mToolbar); + // 这个小于4.0版本是默认为true,在4.0及其以上是false。该方法的作用:决定左上角的图标是否可以点击(没有向左的小图标),true 可点 + getSupportActionBar().setHomeButtonEnabled(true); + // 决定左上角图标的左侧是否有向左的小箭头,true 有小箭头 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); + + loginViewModel = new ViewModelProvider(this).get(TinyRSSUserViewModel.class); + + final EditText hostEditText = findViewById(R.id.tiny_rss_host_edittext); + final EditText usernameEditText = findViewById(R.id.tiny_rss_username_edittext); + final EditText passwordEditText = findViewById(R.id.tiny_rss_password_edittext); + final Button loginButton = findViewById(R.id.tiny_rss_login_button); + final ProgressBar loadingProgressBar = findViewById(R.id.tiny_rss_loading); + + + TextWatcher afterTextChangedListener = 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) { + } + + @Override + public void afterTextChanged(Editable s) { + loginViewModel.loginDataChanged( + hostEditText.getText().toString(), + usernameEditText.getText().toString(), + passwordEditText.getText().toString() + ); + } + }; + hostEditText.addTextChangedListener(afterTextChangedListener); + usernameEditText.addTextChangedListener(afterTextChangedListener); + passwordEditText.addTextChangedListener(afterTextChangedListener); + + /** + * 需要注意的是 setOnEditorActionListener这个方法,并不是在我们点击EditText的时候触发, + * 也不是在我们对EditText进行编辑时触发,而是在我们编辑完之后点击软键盘上的各种键才会触发。 + * 因为通过布局文件中的imeOptions可以控制软件盘右下角的按钮显示为不同按钮。所以和EditorInfo搭配起来可以实现各种软键盘的功能。 + */ + passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + loadingProgressBar.setVisibility(View.VISIBLE); + loginViewModel.login( + hostEditText.getText().toString(), + usernameEditText.getText().toString(), + passwordEditText.getText().toString() + ); + } + return false; + } + }); + + loginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + loadingProgressBar.setVisibility(View.VISIBLE); + loginButton.setEnabled(false); + loginViewModel.login( + hostEditText.getText().toString(), + usernameEditText.getText().toString(), + passwordEditText.getText().toString() + ); + } + }); + + loginViewModel.getLoginFormLiveData().observe(this, new Observer() { + @Override + public void onChanged(@Nullable LoginFormState loginFormState) { + if (loginFormState == null) { + return; + } + loginButton.setEnabled(loginFormState.isDataValid()); + + if (loginFormState.getHostHint() != null) { + hostEditText.setError(getString(loginFormState.getHostHint())); + } + if (loginFormState.getUsernameHint() != null) { + usernameEditText.setError(getString(loginFormState.getUsernameHint())); + } + if (loginFormState.getPasswordHint() != null) { + passwordEditText.setError(getString(loginFormState.getPasswordHint())); + } + } + }); + + loginViewModel.getLoginResult().observe(this, new Observer() { + @Override + public void onChanged(@Nullable LoginResult loginResult) { + if (loginResult == null) { + return; + } + loginButton.setEnabled(true); + loadingProgressBar.setVisibility(View.GONE); + String tips = getString(R.string.welcome); + if (loginResult.isSuccess()) { + ToastUtils.show(tips); + KLog.e( tips + loginResult.getData() ); + setResult(App.ActivityResult_LoginPageToProvider); + finish(); + overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); + } else { + ToastUtils.show(loginResult.getData()); + } + } + }); + } + public Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { + return mColorfulBuilder; + } +} diff --git a/app/src/main/java/me/wizos/loread/adapter/ArticlePagedListAdapter.java b/app/src/main/java/me/wizos/loread/adapter/ArticlePagedListAdapter.java new file mode 100644 index 0000000..f93ce2d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/adapter/ArticlePagedListAdapter.java @@ -0,0 +1,230 @@ +package me.wizos.loread.adapter; + +import android.content.Context; +import android.text.Html; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.paging.PagedListAdapter; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.LazyHeaders; +import com.bumptech.glide.request.RequestOptions; +import com.carlt.networklibs.NetType; +import com.carlt.networklibs.utils.NetworkUtils; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.utils.TimeUtil; +import me.wizos.loread.view.IconFontView; + +public class ArticlePagedListAdapter extends PagedListAdapter { + private RequestOptions canDownloadOptions; + private RequestOptions cannotDownloadOptions; + private Context context; + + public ArticlePagedListAdapter() { + super(DIFF_CALLBACK); + } + + @NonNull + public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) { + context = parent.getContext(); + canDownloadOptions = new RequestOptions() + .centerCrop() + .onlyRetrieveFromCache(false) + .priority(Priority.HIGH); + cannotDownloadOptions = new RequestOptions() + .centerCrop() + .onlyRetrieveFromCache(true) + .priority(Priority.HIGH); + return new ArticleViewHolder(LayoutInflater.from(context).inflate(R.layout.activity_main_list_item, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ArticleViewHolder holder, int position) { + Article article = getItem(position); + if (article != null) { + holder.bindTo(article); + } + else { + // Null defines a placeholder item - PagedListAdapter automatically invalidates this row when the actual object is loaded from the database. + holder.placeholder(); + } + } + + private static DiffUtil.ItemCallback
DIFF_CALLBACK = new DiffUtil.ItemCallback
() { + @Override + public boolean areItemsTheSame(Article oldArticle, Article newArticle) { + return oldArticle.getId().equals(newArticle.getId()); + } + + @Override + public boolean areContentsTheSame(Article oldArticle, Article newArticle) { + return oldArticle.getReadStatus() == newArticle.getReadStatus() + && oldArticle.getStarStatus() == newArticle.getStarStatus() + && oldArticle.getSaveStatus() == newArticle.getSaveStatus() + && oldArticle.getTitle().equals(newArticle.getTitle()) + && (oldArticle.getImage() != null && oldArticle.getImage().equals(newArticle.getImage()) ) + && oldArticle.getSummary().equals(newArticle.getSummary()); + } + }; + + + class ArticleViewHolder extends RecyclerView.ViewHolder { + @NonNull + TextView articleTitle; + TextView articleSummary; + TextView articleFeed; + TextView articlePublished; + IconFontView articleStar; + IconFontView articleReading; + IconFontView articleSave; + ImageView articleImg; + + ArticleViewHolder(@NonNull View itemView) { + super(itemView); + articleTitle = (TextView) itemView.findViewById(R.id.main_slv_item_title); + articleSummary = (TextView) itemView.findViewById(R.id.main_slv_item_summary); + articleFeed = (TextView) itemView.findViewById(R.id.main_slv_item_author); + articleImg = (ImageView) itemView.findViewById(R.id.main_slv_item_img); + articlePublished = (TextView) itemView.findViewById(R.id.main_slv_item_time); + articleStar = (IconFontView) itemView.findViewById(R.id.main_slv_item_icon_star); + articleReading = (IconFontView) itemView.findViewById(R.id.main_slv_item_icon_reading); + articleSave = (IconFontView) itemView.findViewById(R.id.main_slv_item_icon_save); + } + void placeholder(){ + articleTitle.setText(App.i().getString(R.string.loading)); + articleTitle.setAlpha(0.40f); + + articleSummary.setText(""); +// articleSummary.setVisibility(View.GONE); + articleImg.setVisibility(View.GONE); + + articleSummary.setText(""); +// articleFeed.setVisibility(View.GONE); +// articleFeed.setText(App.i().getString(R.string.loading)); +// articlePublished.setText(""); + articleSave.setVisibility(View.GONE); + articleReading.setVisibility(View.GONE); + articleStar.setVisibility(View.GONE); + } + + + void bindTo(Article article){ + if (TextUtils.isEmpty(article.getTitle())) { + articleTitle.setText(App.i().getString(R.string.no_title)); + } else { + articleTitle.setText(article.getTitle()); + } + if (article.getReadStatus() == App.STATUS_READED) { + articleTitle.setAlpha(0.40f); + } else { + articleTitle.setAlpha(1f); + } + + if (TextUtils.isEmpty(article.getSummary()) || article.getSummary().length() == 0) { + articleSummary.setVisibility(View.GONE); + } else { + articleSummary.setVisibility(View.VISIBLE); + articleSummary.setText(article.getSummary()); + } + + if (!TextUtils.isEmpty(article.getImage())) { + articleImg.setVisibility(View.VISIBLE); + + if ( NetworkUtils.isAvailable() && (!App.i().getUser().isDownloadImgOnlyWifi() || NetworkUtils.getNetType().equals(NetType.WIFI)) ) { + // KLog.e( "数据:" + article.getTitle() + " " + App.Referer+ " " + article.getLink() ); + if (!TextUtils.isEmpty(article.getLink())) { + GlideUrl gliderUrl = new GlideUrl(article.getImage(), new LazyHeaders.Builder().addHeader(App.Referer, article.getLink()).build()); + Glide.with(context).load(gliderUrl).apply(canDownloadOptions).into(articleImg); + } else { + Glide.with(context).load(article.getImage()).apply(canDownloadOptions).into(articleImg); + } + } else { + Glide.with(context).load(article.getImage()).apply(cannotDownloadOptions).into(articleImg); + } + } else { + articleImg.setVisibility(View.GONE); + } + + Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(),article.getFeedId()); + if (feed != null && !TextUtils.isEmpty(feed.getTitle())) { + articleFeed.setText(Html.fromHtml(feed.getTitle())); + } else { + articleFeed.setText(article.getFeedTitle()); + } + + articlePublished.setText(TimeUtil.format(article.getPubDate(), "yyyy-MM-dd HH:mm")); + + if (App.STATUS_NOT_FILED == article.getSaveStatus()) { + articleSave.setVisibility(View.GONE); + } else { + articleSave.setVisibility(View.VISIBLE); + } + + if (article.getReadStatus() == App.STATUS_UNREADING) { + articleReading.setVisibility(View.VISIBLE); + } else { + articleReading.setVisibility(View.GONE); + } + if (article.getStarStatus() == App.STATUS_STARED) { + articleStar.setVisibility(View.VISIBLE); + } else { + articleStar.setVisibility(View.GONE); + } + } + } + + /** + * 之所以会产生“更新页面最后几项而下一页前几项会跳动”,是因为: + * 更新页面最后几项时,使用了getItem来获取,而在getItem的默认实现中,会将getItem不为null标识为PagedList的LastKey(需要加载的最后一项)。 + * 但是实际上被修改的项不是视图中的最后一项,所以视图中下一页的前几项会需要重新加载,进而走到onBindViewHolder的getItem。 + * 又因为这几项没有提前被加载到内存中,所以得到的是null,又触发了更新为占位符的逻辑,等到数据加载完了重新渲染时,就产生了跳动的现象。 + */ + private int lastPos = 0; + @Override + public Article getItem(int position) { +// return super.getItem(position); + Article article = super.getItem(position); + if(position < lastPos && lastPos < getItemCount() ){ + super.getItem(lastPos); + }else { + lastPos = position; + } + //KLog.e("加载:" + position + " == " + lastPos + " , " + getCurrentList().getLastKey() + " == "+ getCurrentList().getLoadedCount() + " -- " + (article==null)); + return article; + } + public void setLastItem(int position){ + lastPos = position; + super.getItem(position); + } + public void setLastPos(int position){ + lastPos = position; + } +// public void resetLastItem(int position){ +// if( getItemCount() == 0){ +// return; +// } +// if( position < getItemCount() && position >= 0){ +// super.getItem(position); +// } +// } + public void load(int index){ + if(getCurrentList() !=null && index < getCurrentList().size()){ + getCurrentList().loadAround(index); + } + } +} diff --git a/app/src/main/java/me/wizos/loread/adapter/ArticleViewBinder.java b/app/src/main/java/me/wizos/loread/adapter/ArticleViewBinder.java new file mode 100644 index 0000000..fef6361 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/adapter/ArticleViewBinder.java @@ -0,0 +1,159 @@ +//package me.wizos.loread.adapter; +// +//import android.content.Context; +//import android.text.Html; +//import android.text.TextUtils; +//import android.view.LayoutInflater; +//import android.view.View; +//import android.view.ViewGroup; +//import android.widget.ImageView; +//import android.widget.TextView; +// +//import androidx.annotation.NonNull; +//import androidx.recyclerview.widget.RecyclerView; +// +//import com.bumptech.glide.Glide; +//import com.bumptech.glide.Priority; +//import com.bumptech.glide.load.model.GlideUrl; +//import com.bumptech.glide.load.model.LazyHeaders; +//import com.bumptech.glide.request.RequestOptions; +//import com.carlt.networklibs.NetType; +//import com.carlt.networklibs.utils.NetworkUtils; +//import com.drakeet.multitype.ItemViewBinder; +// +//import me.wizos.loread.App; +//import me.wizos.loread.R; +//import me.wizos.loread.db.Article; +//import me.wizos.loread.db.CoreDB; +//import me.wizos.loread.db.Feed; +//import me.wizos.loread.utils.TimeUtil; +//import me.wizos.loread.view.IconFontView; +// +///** +// * Created by Wizos on 2019/4/14. +// */ +// +//public class ArticleViewBinder extends ItemViewBinder { +// private RequestOptions canDownloadOptions; +// private RequestOptions cannotDownloadOptions; +// private GlideUrl gliderUrl; +// private Context context; +// +// @NonNull +// @Override +// public ArticleViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { +// context = parent.getContext(); +// canDownloadOptions = new RequestOptions() +// .centerCrop() +// .onlyRetrieveFromCache(false) +// .priority(Priority.HIGH); +// cannotDownloadOptions = new RequestOptions() +// .centerCrop() +// .onlyRetrieveFromCache(true) +// .priority(Priority.HIGH); +// return new ArticleViewHolder(inflater.inflate(R.layout.activity_main_list_item, parent, false)); +// } +// +// +// @Override +// public void onBindViewHolder(@NonNull ArticleViewHolder holder, @NonNull Article article) { +// if (TextUtils.isEmpty(article.getTitle())) { +// holder.articleTitle.setText(App.i().getString(R.string.no_title)); +// } else { +// holder.articleTitle.setText(article.getTitle()); +// } +// +// if (TextUtils.isEmpty(article.getSummary()) || article.getSummary().length() == 0) { +// holder.articleSummary.setVisibility(View.GONE); +// } else { +// holder.articleSummary.setVisibility(View.VISIBLE); +// holder.articleSummary.setText(article.getSummary()); +// } +// +// if (!TextUtils.isEmpty(article.getImage())) { +// holder.articleImg.setVisibility(View.VISIBLE); +// +// +// if ( NetworkUtils.isAvailable() && (!App.i().getUser().isDownloadImgOnlyWifi() || NetworkUtils.getNetType().equals(NetType.WIFI)) ) { +// // KLog.e( "数据:" + article.getTitle() + " " + App.Referer+ " " + article.getLink() ); +// if (!TextUtils.isEmpty(article.getLink())) { +// gliderUrl = new GlideUrl(article.getImage(), new LazyHeaders.Builder().addHeader(App.Referer, article.getLink()).build()); +// Glide.with(context).load(gliderUrl).apply(canDownloadOptions).into(holder.articleImg); +// } else { +// Glide.with(context).load(article.getImage()).apply(canDownloadOptions).into(holder.articleImg); +// } +// } else { +// Glide.with(context).load(article.getImage()).apply(cannotDownloadOptions).into(holder.articleImg); +// } +// } else { +// holder.articleImg.setVisibility(View.GONE); +// } +// +// Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), article.getFeedId()); +// if (feed != null && !TextUtils.isEmpty(feed.getTitle())) { +// holder.articleFeed.setText(Html.fromHtml(feed.getTitle())); +// } else { +// holder.articleFeed.setText(article.getFeedTitle()); +// } +// +// holder.articlePublished.setText(TimeUtil.format(article.getPubDate(), "yyyy-MM-dd HH:mm")); +// +// if (article.getReadStatus() == App.STATUS_READED) { +// holder.articleTitle.setAlpha(0.40f); +// } else { +// holder.articleTitle.setAlpha(1f); +// } +// +// if (App.STATUS_NOT_FILED == article.getSaveStatus()) { +// holder.articleSave.setVisibility(View.GONE); +// } else { +// holder.articleSave.setVisibility(View.VISIBLE); +// } +// +// if (article.getReadStatus() == App.STATUS_UNREADING) { +// holder.articleReading.setVisibility(View.VISIBLE); +// } else if (article.getReadStatus() == App.STATUS_UNREAD) { +// holder.articleReading.setVisibility(View.GONE); +// } else { +// holder.articleReading.setVisibility(View.GONE); +// } +// if (article.getStarStatus() == App.STATUS_STARED) { +// holder.articleStar.setVisibility(View.VISIBLE); +// } else { +// holder.articleStar.setVisibility(View.GONE); +// } +// +// } +// +// static class ArticleViewHolder extends RecyclerView.ViewHolder { +// @NonNull +// TextView articleTitle; +// TextView articleSummary; +// TextView articleFeed; +// TextView articlePublished; +// IconFontView articleStar; +// IconFontView articleReading; +// IconFontView articleSave; +// ImageView articleImg; +//// IconFontView markLeft; +//// IconFontView markRight; +//// SwipeDragLayout swipeDragLayout; +// +// ArticleViewHolder(@NonNull View itemView) { +// super(itemView); +// articleTitle = (TextView) itemView.findViewById(R.id.main_slv_item_title); +// articleSummary = (TextView) itemView.findViewById(R.id.main_slv_item_summary); +// articleFeed = (TextView) itemView.findViewById(R.id.main_slv_item_author); +// articleImg = (ImageView) itemView.findViewById(R.id.main_slv_item_img); +// // articleImg.setBackgroundColor(itemView.getContext().getResources().getColor(R.color.placeholder_bg)); +// articlePublished = (TextView) itemView.findViewById(R.id.main_slv_item_time); +// articleStar = (IconFontView) itemView.findViewById(R.id.main_slv_item_icon_star); +// articleReading = (IconFontView) itemView.findViewById(R.id.main_slv_item_icon_reading); +// articleSave = (IconFontView) itemView.findViewById(R.id.main_slv_item_icon_save); +//// markLeft = itemView.findViewById(R.id.main_list_item_menu_left); +//// markRight = itemView.findViewById(R.id.main_list_item_menu_right); +//// swipeDragLayout = itemView.findViewById(R.id.swipe_layout); +// } +// } +// +//} diff --git a/app/src/main/java/me/wizos/loread/adapter/ExpandableListAdapterS.java b/app/src/main/java/me/wizos/loread/adapter/ExpandableListAdapterS.java deleted file mode 100644 index b661bfc..0000000 --- a/app/src/main/java/me/wizos/loread/adapter/ExpandableListAdapterS.java +++ /dev/null @@ -1,330 +0,0 @@ -package me.wizos.loread.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseExpandableListAdapter; -import android.widget.ExpandableListView; -import android.widget.TextView; - -import com.socks.library.KLog; - -import java.util.ArrayList; -import java.util.List; - -import me.wizos.loread.App; -import me.wizos.loread.R; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.db.Feed; -import me.wizos.loread.db.Tag; -import me.wizos.loread.net.Api; -import me.wizos.loread.view.ExpandableListViewS; -import me.wizos.loread.view.IconFontView; - -/** - * @author Wizos on 2017/9/17. - */ - -public class ExpandableListAdapterS extends BaseExpandableListAdapter implements ExpandableListViewS.HeaderAdapter { // implements ExpandableListViewS.HeaderAdapter - private Context context; - private List tags = new ArrayList<>(); - private ExpandableListView listView; -// private List> feeds; - - public ExpandableListAdapterS(Context context, List tags, ExpandableListView listView) { - this.context = context; - this.tags = tags; -// feeds = new ArrayList<>(tags.size()); -// for (int i = 0; i < tags.size(); i++) { -// try { -// feeds.add(i, tags.get(i).getFeeds()); -// } catch (RuntimeException e) { -// KLog.i("无法获取子项:" + i + tags.get(i).getTitle()); -// } -// } - - this.listView = listView; - } - - // 获得某个父项的某个子项 - @Override - public Object getChild(int groupPos, int childPos) { - try { - return tags.get(groupPos).getFeeds().get(childPos); -// return feeds.get(groupPos).get(childPos); - } catch (RuntimeException e) { - KLog.i("无法获取子项B:" + WithDB.i().getFeeds().get(childPos)); - return 0; - } - } - - public void removeChild(int groupPos, Feed feed) { - try { - tags.get(groupPos).getFeeds().remove(feed); -// feeds.get(groupPos).remove(childPos); - } catch (Exception e) { - } - } - - // 获得父项的数量 - @Override - public int getGroupCount() { - return tags.size(); - } - - // 获得某个父项的子项数目 - @Override - public int getChildrenCount(int groupPos) { - try { - return tags.get(groupPos).getFeeds().size(); -// return feeds.get(groupPos).size(); - } catch (RuntimeException e) { -// KLog.e("子项的数量B:" + WithDB.i().getFeeds().size()); - return 0; // WithDB.i().getFeeds().size() - } - } - - // 获得某个父项 - @Override - public Object getGroup(int parentPos) { - KLog.e("getGroup:" + tags.get(parentPos)); - return tags.get(parentPos); - } - - // 获得某个父项的id - @Override - public long getGroupId(int parentPos) { - return parentPos; - } - - // 获得某个父项的某个子项的id - @Override - public long getChildId(int parentPos, int childPos) { - return childPos; - } - - // 按函数的名字来理解应该是是否具有稳定的id,这个方法目前一直都是返回false,没有去改动过 - @Override - public boolean hasStableIds() { - return false; - } - - - public class ItemViewHolder { - public String id; // 当前这个 group(Tag) 或者 child(Feed) 的 id - public int type; // 当前这个 Item 是 group 还是 child - public int groupPos; - public int childPos; - public final static int TYPE_GROUP = 0; - public final static int TYPE_CHILD = 1; - - IconFontView icon; - TextView title; - TextView count; - } - -// private ArrayMap> stream = new ArrayMap<>(); -// public void updateData( ArrayMap> stream){ -// this.stream = stream; -// } - - - - @Override - public View getGroupView(final int groupPos, final boolean isExpanded, View convertView, final ViewGroup parent) { - ItemViewHolder groupViewHolder; - // 使用一个 ViewHolder,可减少在该函数中每次都要去 findViewById ,这很费时间。具体见:https://zhidao.baidu.com/question/544207312.html - if (convertView == null) { - groupViewHolder = new ItemViewHolder(); - convertView = LayoutInflater.from(context).inflate(R.layout.tag_expandable_item_group, null); - groupViewHolder.icon = convertView.findViewById(R.id.group_item_icon); - groupViewHolder.title = convertView.findViewById(R.id.group_item_title); - groupViewHolder.count = convertView.findViewById(R.id.group_item_count); - convertView.setTag(groupViewHolder); - } else { - groupViewHolder = (ItemViewHolder) convertView.getTag(); - } - - if (isExpanded) { - groupViewHolder.icon.setText(context.getString(R.string.font_arrow_down)); - } else { - groupViewHolder.icon.setText(context.getString(R.string.font_arrow_right)); - } - - groupViewHolder.icon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { -// KLog.e("箭头被点击"); - if (isExpanded) { - ((ExpandableListViewS) parent).collapseGroup(groupPos); - ((IconFontView) v).setText(context.getString(R.string.font_arrow_right)); - } else { - ((ExpandableListViewS) parent).expandGroup(groupPos); - ((IconFontView) v).setText(context.getString(R.string.font_arrow_down)); - } - } - }); - - - try { - Tag tag = tags.get(groupPos); - if (tag.getFeeds().size() == 0) { - groupViewHolder.icon.setText(context.getString(R.string.font_tag)); - } - groupViewHolder.id = tag.getId(); - groupViewHolder.type = ItemViewHolder.TYPE_GROUP; - groupViewHolder.groupPos = groupPos; - groupViewHolder.title.setText(tag.getTitle()); - - // 方法一 - int count = 0; - if (tag.getId().contains(Api.U_READING_LIST)) { - count = WithDB.i().getUnreadArtsCount(); - } else if (tag.getId().contains(Api.U_NO_LABEL)) { - count = WithDB.i().getUnreadArtsCountNoTag(); - } else { - count = tag.getUnreadCount(); -// count = WithDB.i().getUnreadArtsCountByTag(tag.getId()); - } - groupViewHolder.count.setText(String.valueOf(count)); - - // 方法2 -// groupViewHolder.count.setText(String.valueOf( UnreadCountUtil.getTagUnreadCount(tag.getId()) )); -// count = UnreadCountUtil.getTagUnreadCount(tag.getId()); - - - groupViewHolder.count.setText(String.valueOf(count)); - groupViewHolder.count.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE); - - } catch (Exception e) { - groupViewHolder.id = ""; - groupViewHolder.type = ItemViewHolder.TYPE_GROUP; - groupViewHolder.groupPos = groupPos; - groupViewHolder.title.setText(App.i().getString(R.string.item_error)); -// KLog.e("父分类:" + theTag.getTitle() + "--" + theTag.getUnreadcount()); - KLog.e("出错了"); - KLog.e(e); - } - return convertView; - } - - - // 获得子项显示的view - @Override - public View getChildView(int groupPos, int childPos, boolean isExpanded, View convertView, final ViewGroup parent) { - ItemViewHolder childViewHolder; - if (convertView == null) { - childViewHolder = new ItemViewHolder(); - convertView = LayoutInflater.from(context).inflate(R.layout.tag_expandable_item_child, null); -// childViewHolder.icon = (IconFontView) convertView.findViewById(R.id.group_item_icon); - childViewHolder.title = convertView.findViewById(R.id.child_item_title); - childViewHolder.count = convertView.findViewById(R.id.child_item_count); - convertView.setTag(childViewHolder); - } else { - childViewHolder = (ItemViewHolder) convertView.getTag(); - } - - try { - final Feed feed = tags.get(groupPos).getFeeds().get(childPos); - childViewHolder.id = feed.getId(); - childViewHolder.type = ItemViewHolder.TYPE_CHILD; - childViewHolder.groupPos = groupPos; - childViewHolder.childPos = childPos; - childViewHolder.title.setText(feed.getTitle()); - - Integer count; - count = feed.getUnreadCount(); - childViewHolder.count.setText(String.valueOf(count)); - childViewHolder.count.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE); - } catch (RuntimeException e) { - childViewHolder.id = ""; - childViewHolder.type = ItemViewHolder.TYPE_CHILD; - childViewHolder.groupPos = groupPos; - childViewHolder.childPos = childPos; - childViewHolder.title.setText(App.i().getString(R.string.item_error)); - KLog.e("出错了"); - KLog.e(e); - } - return convertView; - } - - -// private ArrayMap map = new ArrayMap<>(); -// private QueryTask queryTask = null; - // Params, Progress, Result -// private static class QueryTask extends AsyncTask { -// private WeakReference mAdapter; -// private ItemViewHolder childViewHolder; -// private String feedId; -// -// QueryTask(ExpandableListAdapterS adapter, ItemViewHolder childViewHolder) { -// mAdapter = new WeakReference<>(adapter); -// this.childViewHolder = childViewHolder; -// } -// -// @Override -// protected Integer doInBackground(String... params) { -// if (isCancelled()) { -// return 0; -// } -// feedId = params[0]; -//// int count = WithDB.i().getUnreadArtsCountByFeed(feedId); -//// publishProgress(feedId,count+""); -// //返回结果 -// return WithDB.i().getUnreadArtsCountByFeed(feedId); -// } -// -// /** -// * 在doInbackground之后执行 -// */ -// @Override -// protected void onPostExecute(Integer count) { -// try { -// if (!childViewHolder.id.equals(feedId)) { -// KLog.e(feedId + "获取错乱了"); -// return; -// } -// KLog.e(feedId + "获取正常的:" + count); -// childViewHolder.count.setText(count + ""); -// childViewHolder.count.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE); -// } catch (Exception e) { -// KLog.e("出错了"); -// e.printStackTrace(); -// } -// } -// } - - - // 子项是否可选中,如果需要设置子项的点击事件,需要返回true - @Override - public boolean isChildSelectable(int i, int i1) { - return true; - } - - // 根据以下2个案例修改而成 - // https://github.com/qianxin2016/DockingExpandableListView/blob/master/app/src/main/java/com/xinxin/dockingexpandablelistview/adapter/DockingExpandableListViewAdapter.java - // https://github.com/CotTan/PinnedHeaderExpandable/blob/master/app/src/main/java/intelstd/com/pinnedheaderexpandable/PinnedHeaderExpandableListView.java - @Override - public int getHeaderState(int firstVisibleGroupPosition, int firstVisibleChildPosition) { - // 如果这个 group 不包含 child 并且没有展开,那么不绘制 header view - if (firstVisibleChildPosition == -1 && !listView.isGroupExpanded(firstVisibleGroupPosition)) { - return PINNED_HEADER_GONE; - } - // 到达当前 Group 的最后一个 Child,准备对接下一个 Group header。 - if (firstVisibleChildPosition == getChildrenCount(firstVisibleGroupPosition) - 1) { - return PINNED_HEADER_PUSHED_UP; - } - return PINNED_HEADER_VISIBLE; - } - - - @Override - public void configureHeader(View header, int groupPosition, int childPosition, int alpha) { -// String groupTitle = tags.get(groupPosition).getTitle(); - ((TextView) header.findViewById(R.id.header_item_title)).setText(tags.get(groupPosition).getTitle()); -// ((TextView) header.findViewById(R.id.header_item_count)).setText(tags.get(groupPosition).getUnreadCount()+""); -// KLog.e("数字:" + tags.get(groupPosition).getUnreadCount() ); - } - -} diff --git a/app/src/main/java/me/wizos/loread/adapter/ExpandedAdapter.java b/app/src/main/java/me/wizos/loread/adapter/ExpandedAdapter.java new file mode 100644 index 0000000..98732d1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/adapter/ExpandedAdapter.java @@ -0,0 +1,207 @@ +package me.wizos.loread.adapter; + +import android.content.Context; +import android.util.ArrayMap; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.socks.library.KLog; +import com.yanzhenjie.recyclerview.ExpandableAdapter; + +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.db.Collection; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.view.IconFontView; + +/** + * Created by Wizos on 2019/4/17. + */ + +public class ExpandedAdapter extends ExpandableAdapter { + private LayoutInflater mInflater; + private List categories; + //private int countMode = App.STATUS_ALL; // 0为所有, 1为unread, 2为star + private ArrayMap> feedsMap = new ArrayMap<>(); + + public ExpandedAdapter(Context context) { + this.mInflater = LayoutInflater.from(context); + } +// public ExpandedAdapter(Context context, int countMode) { +// this.mInflater = LayoutInflater.from(context); +// this.countMode = countMode; +// } + + public void setParents(List parents) { + this.categories = parents; + } + public List getParents() { + return categories; + } + + + public void notifyDataChanged() { + //KLog.e("获得notifyDataSetChanged"); + feedsMap = new ArrayMap<>(); + super.notifyDataSetChanged(); + } + + + public Collection getGroup(int groupPos){ + return categories.get(groupPos); + } + + public Collection getChild(int groupPos, int childPos) { + return getChildren(groupPos).get(childPos); + } + + private List getChildren(int groupPos) { + //KLog.e("getFeeds"); + if (null == feedsMap.get(categories.get(groupPos).getId())) { + long time = System.currentTimeMillis(); + List feedWraps; + + if( App.i().getUser().getStreamStatus() == App.STATUS_UNREAD ){ + feedWraps = CoreDB.i().feedDao().getFeedsUnreadCountByCategoryId(App.i().getUser().getId(), categories.get(groupPos).getId()); + }else if( App.i().getUser().getStreamStatus() == App.STATUS_STARED ){ + feedWraps = CoreDB.i().feedDao().getFeedsStarCountByCategoryId(App.i().getUser().getId(), categories.get(groupPos).getId()); + }else { + feedWraps = CoreDB.i().feedDao().getFeedsAllCountByCategoryId(App.i().getUser().getId(), categories.get(groupPos).getId()); + } + + long d = System.currentTimeMillis() - time; + KLog.e("返回feedList,耗时:" + d + " = " + App.i().getUser().getId() + " = " + categories.get(groupPos).getId() ); + feedsMap.put(categories.get(groupPos).getId(), feedWraps); + + return feedWraps; + } + return feedsMap.get(categories.get(groupPos).getId()); + } + + @Override + public int parentItemCount() { +// KLog.e("parentItemCount"); + return categories == null ? 0 : categories.size(); + } + + @Override + public int childItemCount(int parentPosition) { + //KLog.e("childItemCount"); + List memberList = getChildren(parentPosition); + return memberList == null ? 0 : memberList.size(); + } + + @Override + public RecyclerView.ViewHolder createParentHolder(@NonNull ViewGroup root, int viewType) { +// KLog.e("createParentHolder"); + View view = mInflater.inflate(R.layout.tag_expandable_item_group, root, false); + return new ParentHolder(view); + } + + @Override + public RecyclerView.ViewHolder createChildHolder(@NonNull ViewGroup root, int viewType) { + //KLog.e("createChildHolder"); + View view = mInflater.inflate(R.layout.tag_expandable_item_child, root, false); + return new ChildHolder(view); + } + + @Override + public void bindParentHolder(@NonNull RecyclerView.ViewHolder holder, int position) { +// KLog.e("bindParentHolder"); + ((ParentHolder) holder).setData(this, categories.get(position), position); + } + + @Override + public void bindChildHolder(@NonNull RecyclerView.ViewHolder holder, int parentPosition, int position) { + //KLog.e("bindChildHolder"); + ((ChildHolder) holder).setData(getChildren(parentPosition).get(position)); + } + + static class ParentHolder extends RecyclerView.ViewHolder { + Context context; + IconFontView icon; + TextView title; + TextView countView; + ExpandedAdapter adapter; + + ParentHolder(@NonNull View itemView) { + super(itemView); + icon = itemView.findViewById(R.id.group_item_icon); + title = itemView.findViewById(R.id.group_item_title); + countView = itemView.findViewById(R.id.group_item_count); + context = itemView.getContext(); + } + + public void setData(@NonNull ExpandedAdapter mAdapter, @NonNull Collection category, @NonNull final int parentPosition) { +// KLog.e("设置父的数据A:" + category); + adapter = mAdapter; +// if(!category.getId().contains(App.CATEGORY_ALL) && !category.getId().contains(App.CATEGORY_UNCATEGORIZED)){ +// category = CoreDB.i().categoryDao().getById(App.i().getUser().getId(), category.getId()); +// } + + if (CoreDB.i().feedCategoryDao().getCountByCategoryId(App.i().getUser().getId(), category.getId()) == 0) { + icon.setText(context.getString(R.string.font_tag)); + } else if (adapter.isExpanded(parentPosition)) { + icon.setText(context.getString(R.string.font_arrow_down)); + } else { + icon.setText(context.getString(R.string.font_arrow_right)); + } + +// KLog.e("设置父的数据B "); + title.setText(category.getTitle()); + + int count = category.getCount(); + if (count > 0) { + countView.setText(String.valueOf(count)); + countView.setVisibility(View.VISIBLE); + } else { + countView.setVisibility(View.INVISIBLE); + } + + + icon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + long time = System.currentTimeMillis(); + // 判断parent是否打开了二级菜单 + if (adapter.isExpanded(parentPosition)) { + // 关闭该parent下的二级菜单 + adapter.collapseParent(parentPosition); + icon.setText(context.getString(R.string.font_arrow_right)); + } else { + // 打开该parent下的二级菜单 + adapter.expandParent(parentPosition); + icon.setText(context.getString(R.string.font_arrow_down)); + } + KLog.e("点击展开收缩:" + (System.currentTimeMillis() - time) ); + } + }); + } + } + + static class ChildHolder extends RecyclerView.ViewHolder { + TextView title; + TextView countView; + + ChildHolder(@NonNull View itemView) { + super(itemView); + title = itemView.findViewById(R.id.child_item_title); + countView = itemView.findViewById(R.id.child_item_count); + } + + public void setData(Collection feed) { + //feed.refresh(); + title.setText(feed.getTitle()); + int count = feed.getCount(); + countView.setText(String.valueOf(count)); + countView.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE); + } + } +} diff --git a/app/src/main/java/me/wizos/loread/adapter/MainListViewAdapter.java b/app/src/main/java/me/wizos/loread/adapter/MainListViewAdapter.java deleted file mode 100644 index 0fb165d..0000000 --- a/app/src/main/java/me/wizos/loread/adapter/MainListViewAdapter.java +++ /dev/null @@ -1,268 +0,0 @@ -package me.wizos.loread.adapter; - - -import android.content.Context; -import android.text.Html; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.Priority; -import com.bumptech.glide.load.model.GlideUrl; -import com.bumptech.glide.load.model.LazyHeaders; -import com.bumptech.glide.request.RequestOptions; -import com.ditclear.swipelayout.SwipeDragLayout; - -import java.util.List; - -import me.wizos.loread.R; -import me.wizos.loread.db.Article; -import me.wizos.loread.net.Api; -import me.wizos.loread.utils.NetworkUtil; -import me.wizos.loread.utils.TimeUtil; -import me.wizos.loread.view.IconFontView; -import me.wizos.loread.view.ListView.ListViewS; - - -/** - * @author Wizos on 2016/3/15. - */ -public class MainListViewAdapter extends ArrayAdapter
{ - private List
articleList; - private Context context; - private ListViewS slv; - private RequestOptions canDownloadOptions; - private RequestOptions cannotDownloadOptions; - private GlideUrl gliderUrl; -// private Headers headers; - - public MainListViewAdapter(Context context, List
articleList, ListViewS slv) { - super(context, 0, articleList); - this.context = context; - this.articleList = articleList; - this.slv = slv; - canDownloadOptions = new RequestOptions() - .centerCrop() - .onlyRetrieveFromCache(false) - .priority(Priority.HIGH); - cannotDownloadOptions = new RequestOptions() - .centerCrop() - .onlyRetrieveFromCache(true) - .priority(Priority.HIGH); - } - @Override - public int getCount() { - return articleList.size(); - } - - @Override - public Article getItem(int position) { - try { - return articleList.get(position); - } catch (Exception e) { - e.printStackTrace(); - return articleList.get(position); - } - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - final Article article = this.getItem(position); - if (convertView == null) { - cvh = new CustomViewHolder(); - convertView = LayoutInflater.from(context).inflate(R.layout.activity_main_list_item, null); - cvh.articleTitle = convertView.findViewById(R.id.main_slv_item_title); - cvh.articleSummary = convertView.findViewById(R.id.main_slv_item_summary); - cvh.articleFeed = convertView.findViewById(R.id.main_slv_item_author); - cvh.articleImg = convertView.findViewById(R.id.main_slv_item_img); - cvh.articleImg.setBackgroundColor(getContext().getResources().getColor(R.color.placeholder_bg)); - cvh.articlePublished = convertView.findViewById(R.id.main_slv_item_time); - cvh.articleStar = convertView.findViewById(R.id.main_slv_item_icon_star); - cvh.articleReading = convertView.findViewById(R.id.main_slv_item_icon_reading); - cvh.articleSave = convertView.findViewById(R.id.main_slv_item_icon_save); - cvh.markLeft = convertView.findViewById(R.id.main_list_item_menu_left); - cvh.markRight = convertView.findViewById(R.id.main_list_item_menu_right); - swipeDragLayout = convertView.findViewById(R.id.swipe_layout); - swipeDragLayout.addListener(slv); - - convertView.setTag(cvh); - } else { - cvh = (CustomViewHolder) convertView.getTag(); - } - - cvh.articleTitle.setText(Html.fromHtml(article.getTitle())); - if (article.getSummary().length() == 0) { - cvh.articleSummary.setVisibility(View.GONE); - } else { - cvh.articleSummary.setVisibility(View.VISIBLE); - cvh.articleSummary.setText(article.getSummary()); - } - - if (!TextUtils.isEmpty(article.getCoverSrc())) { - cvh.articleImg.setVisibility(View.VISIBLE); - -// String idInMD5 = StringUtil.str2MD5(article.getId()); -// String coverPath = App.externalFilesDir + "/cache/" + idInMD5 + "/"; -// String fileName = idInMD5 + StringUtil.getImageSuffix(article.getCoverSrc()); -// if (new File(coverPath + fileName).exists()) { -// Glide.with(context).load(coverPath + fileName).centerCrop().into(cvh.articleImg); // diskCacheStrategy(DiskCacheStrategy.NONE). -// } - -// KLog.e("网络", Tool.canDownImg() ); - if (NetworkUtil.canDownImg()) { -// headers = new Headers(){ -// @Override -// public Map getHeaders() { -// ArrayMap header = new ArrayMap<>(); -// header.put("Referer", article.getCanonical()); -// return header; -// } -// }; - gliderUrl = new GlideUrl(article.getCoverSrc(), new LazyHeaders.Builder().addHeader(Api.Referer, article.getCanonical()).build()); - Glide.with(context).load(gliderUrl).apply(canDownloadOptions).into(cvh.articleImg); // diskCacheStrategy(DiskCacheStrategy.NONE). - } else { - Glide.with(context).load(article.getCoverSrc()).apply(cannotDownloadOptions).into(cvh.articleImg); - } - } else { - cvh.articleImg.setVisibility(View.GONE); - } - - if (article.getOriginTitle() != null) { - cvh.articleFeed.setText(Html.fromHtml(article.getOriginTitle())); - } - cvh.articlePublished.setText(TimeUtil.stampToTime(article.getPublished() * 1000, "yyyy-MM-dd HH:mm")); - - if (article.getReadStatus() == Api.READED) { - cvh.articleTitle.setAlpha(0.40f); - } else { - cvh.articleTitle.setAlpha(1f); - } - - if (article.getReadStatus() == Api.UNREADING) { - cvh.articleReading.setVisibility(View.VISIBLE); - cvh.markRight.setText(context.getResources().getString(R.string.font_readed)); - } else if (article.getReadStatus() == Api.UNREAD) { - cvh.articleReading.setVisibility(View.GONE); - cvh.markRight.setText(context.getResources().getString(R.string.font_readed)); - } else { - cvh.articleReading.setVisibility(View.GONE); - cvh.markRight.setText(context.getResources().getString(R.string.font_unread)); - } - if (article.getStarStatus() == Api.STARED) { - cvh.articleStar.setVisibility(View.VISIBLE); - cvh.markLeft.setText(context.getResources().getString(R.string.font_unstar)); - } else { - cvh.articleStar.setVisibility(View.GONE); - cvh.markLeft.setText(context.getResources().getString(R.string.font_stared)); - } - if (Api.SAVE_DIR_CACHE.equals(article.getSaveDir())) { - cvh.articleSave.setVisibility(View.GONE); - } else { - cvh.articleSave.setVisibility(View.VISIBLE); - } - return convertView; - } - - -// private void downCoverImage(final String coverPath, final String fileName, final Article article,final String idInMD5, final View view) { -// // 2018/4/14 如果发现文章正文的图片有下载,就直接用?不行,因为根据CoverSrc找不到这个图片 -// -// FileCallback fileCallback = new FileCallback(coverPath, fileName + Api.EXT_TMP) { -// @Override -// public void onSuccess(final Response response) { -//// KLog.e("图片下载成功,开始压缩" + width + " 高度" + height ); -// if (!response.isSuccessful()) { -// onError(response); -// return; -// } -// -// AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { -// @Override public void run() { -// try { -// -// FileUtil.copy(new File(coverPath + fileName + Api.EXT_TMP), new File(coverPath + idInMD5 + "_files/0-" + StringUtil.getFileNameExtByUrl(article.getCoverSrc())) ); -// new File(coverPath + fileName + Api.EXT_TMP).renameTo(new File(coverPath + fileName)); -// -// Luban.with(context) -// .load(coverPath + fileName) -// .ignoreBy(50) -// .setCompressListener(new OnCompressListener() { -// @Override -// public void onStart() { -// } -// -// @Override -// public void onSuccess(File file) { -// KLog.e("压缩成了" + file.getAbsolutePath() ); -// file.renameTo(new File(coverPath + fileName)); -// MainListViewAdapter.this.notifyDataSetChanged(); -// } -// -// @Override -// public void onError(Throwable e) { -// } -// }).launch(); -// }catch (IOException e){ -// e.printStackTrace(); -// } -// KLog.e("当前线程是:" + Thread.currentThread()); -// } -// }); -// -// } -// -// @Override -// public void onError(Response response) { -// new File(coverPath + fileName + Api.EXT_TMP).delete(); -// } -// }; -// -// Request request = OkGo.get(article.getCoverSrc()) -// .client(App.imgHttpClient); -// Feed feed = WithDB.i().getFeed(article.getOriginStreamId()); -// if (feed != null && null != feed.getConfig()) { -// FeedConfig feedConfig = feed.getConfig(); -// String referer = feedConfig.getReferer(); -// if (TextUtils.isEmpty(referer)) { -// } else if (Api.Auto.equals(referer)) { -// request.headers(Api.Referer, article.getCanonical()); -// } else { -// request.headers(Api.Referer, referer); -// } -// String userAgent = feedConfig.getUserAgent(); -// if (!TextUtils.isEmpty(userAgent)) { -// request.headers(Api.UserAgent, userAgent); -// } -// } -// request.execute(fileCallback); -// } - - - - private CustomViewHolder cvh; - private SwipeDragLayout swipeDragLayout; - public class CustomViewHolder { - TextView articleTitle; - TextView articleSummary; - TextView articleFeed; - TextView articlePublished; - IconFontView articleStar; - IconFontView articleReading; - IconFontView articleSave; - ImageView articleImg; - - public IconFontView markLeft; - public IconFontView markRight; - } - -} diff --git a/app/src/main/java/me/wizos/loread/adapter/MaterialSimpleListAdapter.java b/app/src/main/java/me/wizos/loread/adapter/MaterialSimpleListAdapter.java deleted file mode 100644 index 9a6242b..0000000 --- a/app/src/main/java/me/wizos/loread/adapter/MaterialSimpleListAdapter.java +++ /dev/null @@ -1,68 +0,0 @@ -package me.wizos.loread.adapter; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.afollestad.materialdialogs.internal.MDAdapter; - -import me.wizos.loread.R; - -/** - * See the sample project to understand how this is used. Mimics the Simple List dialog style - * displayed on Google's guidelines site: https://www.google.com/design/spec/components/dialogs.html#dialogs-simple-dialogs - * - * @author Aidan Follestad (afollestad) - */ -public class MaterialSimpleListAdapter extends ArrayAdapter implements MDAdapter { - - private MaterialDialog dialog; - - public MaterialSimpleListAdapter(Context context) { - super(context, R.layout.md_simplelist_item, android.R.id.title); - } - - @Override - public void setDialog(MaterialDialog dialog) { - this.dialog = dialog; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(final int index, View convertView, ViewGroup parent) { - final View view = super.getView(index, convertView, parent); - if (dialog != null) { - final MaterialSimpleListItem item = getItem(index); - ImageView ic = (ImageView) view.findViewById(android.R.id.icon); - if (item.getIcon() != null) { - ic.setImageDrawable(item.getIcon()); - ic.setPadding(item.getIconPadding(), item.getIconPadding(), - item.getIconPadding(), item.getIconPadding()); - ic.getBackground().setColorFilter(item.getBackgroundColor(), - PorterDuff.Mode.SRC_ATOP); - } else { - ic.setVisibility(View.GONE); - } - TextView tv = (TextView) view.findViewById(android.R.id.title); - tv.setTextColor(dialog.getBuilder().getItemColor()); - tv.setText(item.getContent()); - dialog.setTypeface(tv, dialog.getBuilder().getRegularFont()); - } - return view; - } - -} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/adapter/MaterialSimpleListItem.java b/app/src/main/java/me/wizos/loread/adapter/MaterialSimpleListItem.java deleted file mode 100644 index e078b9d..0000000 --- a/app/src/main/java/me/wizos/loread/adapter/MaterialSimpleListItem.java +++ /dev/null @@ -1,138 +0,0 @@ -package me.wizos.loread.adapter; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.DimenRes; -import android.support.annotation.DrawableRes; -import android.support.annotation.IntRange; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; -import android.util.TypedValue; - -import com.afollestad.materialdialogs.util.DialogUtils; - -/** - * @author Aidan Follestad (afollestad) - */ -public class MaterialSimpleListItem { - - private final Builder mBuilder; - - private MaterialSimpleListItem(Builder builder) { - mBuilder = builder; - } - - public Drawable getIcon() { - return mBuilder.mIcon; - } - - public CharSequence getContent() { - return mBuilder.mContent; - } - - public int getIconPadding() { - return mBuilder.mIconPadding; - } - - @ColorInt - public int getBackgroundColor() { - return mBuilder.mBackgroundColor; - } - - public long getId() { - return mBuilder.mId; - } - - @Nullable - public Object getTag() { - return mBuilder.mTag; - } - - public static class Builder { - - private final Context mContext; - protected Drawable mIcon; - protected CharSequence mContent; - protected int mIconPadding; - protected int mBackgroundColor; - protected long mId; - protected Object mTag; - - public Builder(Context context) { - mContext = context; - mBackgroundColor = Color.parseColor("#BCBCBC"); - } - - public Builder icon(Drawable icon) { - this.mIcon = icon; - return this; - } - - public Builder icon(@DrawableRes int iconRes) { - return icon(ContextCompat.getDrawable(mContext, iconRes)); - } - - public Builder iconPadding(@IntRange(from = 0, to = Integer.MAX_VALUE) int padding) { - this.mIconPadding = padding; - return this; - } - - public Builder iconPaddingDp(@IntRange(from = 0, to = Integer.MAX_VALUE) int paddingDp) { - this.mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingDp, - mContext.getResources().getDisplayMetrics()); - return this; - } - - public Builder iconPaddingRes(@DimenRes int paddingRes) { - return iconPadding(mContext.getResources().getDimensionPixelSize(paddingRes)); - } - - public Builder content(CharSequence content) { - this.mContent = content; - return this; - } - - public Builder content(@StringRes int contentRes) { - return content(mContext.getString(contentRes)); - } - - public Builder backgroundColor(@ColorInt int color) { - this.mBackgroundColor = color; - return this; - } - - public Builder backgroundColorRes(@ColorRes int colorRes) { - return backgroundColor(DialogUtils.getColor(mContext, colorRes)); - } - - public Builder backgroundColorAttr(@AttrRes int colorAttr) { - return backgroundColor(DialogUtils.resolveColor(mContext, colorAttr)); - } - - public Builder id(long id) { - mId = id; - return this; - } - - public Builder tag(@Nullable Object tag) { - mTag = tag; - return this; - } - - public MaterialSimpleListItem build() { - return new MaterialSimpleListItem(this); - } - } - - @Override - public String toString() { - if (getContent() != null) - return getContent().toString(); - else return "(no content)"; - } -} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/adapter/ViewPagerAdapter.java b/app/src/main/java/me/wizos/loread/adapter/ViewPagerAdapter.java deleted file mode 100644 index a9c233b..0000000 --- a/app/src/main/java/me/wizos/loread/adapter/ViewPagerAdapter.java +++ /dev/null @@ -1,235 +0,0 @@ -//package me.wizos.loread.adapter; -// -//import android.support.v4.view.PagerAdapter; -//import android.util.ArrayMap; -//import android.view.View; -//import android.view.ViewGroup; -// -//import com.socks.library.KLog; -// -//import org.jsoup.Jsoup; -//import org.jsoup.nodes.Document; -//import org.jsoup.nodes.Element; -//import org.jsoup.select.Elements; -// -//import java.io.File; -//import java.util.ArrayList; -//import java.util.List; -// -//import me.wizos.loread.App; -//import me.wizos.loread.activity.ArticleActivity; -//import me.wizos.loread.data.PrefUtils; -//import me.wizos.loread.data.WithDB; -//import me.wizos.loread.db.Article; -//import me.wizos.loread.net.Api; -//import me.wizos.loread.utils.FileUtil; -//import me.wizos.loread.utils.HttpUtil; -//import me.wizos.loread.utils.StringUtil; -//import me.wizos.loread.utils.Tool; -//import me.wizos.loread.view.WebViewClientX; -//import me.wizos.loread.view.WebViewX; -// -// -///** -// * Created by Wizos on 2017/6/4. -// * ArticleActivity 内 ViewPager 的适配器 -// */ -// -//public class ViewPagerAdapter extends PagerAdapter { // implements ViewPager.OnPageChangeListener -// private ArticleActivity activity; -// private List
dataList; -// -// public ViewPagerAdapter(ArticleActivity context, List
dataList) { -// if (null == dataList || dataList.isEmpty()) {return;} -// this.activity = context; -// this.dataList = dataList; -// } -// -// -//// /** -//// * 这个方法会在屏幕滚动过程中不断被调用。 -//// * -//// * @param pagePosition 当用手指滑动时,如果手指按在页面上不动,position和当前页面index是一致的; -//// * 如果手指向左拖动时(页面向右翻动),position大部分时间和“当前页面”一致,只有翻页成功的情况下最后一次调用才会变为“目标页面”; -//// * 如果手指向右拖动时(页面向左翻动),position大部分时间和“目标页面”一致,只有翻页不成功的情况下最后一次调用才会变为“原页面”。 -//// *

-//// * 当直接设置setCurrentItem翻页时,如果是相邻的情况(比如现在是第二个页面,跳到第一或者第三个页面): -//// * 如果页面向右翻动,大部分时间是和当前页面是一致的,只有最后才变成目标页面; -//// * 如果页面向左翻动,position和目标页面是一致的。这和用手指拖动页面翻动是基本一致的。 -//// * 如果不是相邻的情况,比如我从第一个页面跳到第三个页面,position先是0,然后逐步变成1,然后逐步变成2; -//// * 我从第三个页面跳到第一个页面,position先是1,然后逐步变成0,并没有出现为2的情况。 -//// * @param positionOffset 当前页面滑动比例,如果页面向右翻动,这个值不断变大,最后在趋近1的情况后突变为0。如果页面向左翻动,这个值不断变小,最后变为0。 -//// * @param positionOffsetPixels 当前页面滑动像素,变化情况和positionOffset一致。 -//// */ -//// @Override -//// public void onPageScrolled(int pagePosition, float positionOffset, int positionOffsetPixels) { -////// judgeDirection(pagePosition); // TEST: -//// } -////// private void judgeDirection(final int pagePosition) { -////// KLog.e("方向", "滑动" + pagePosition + "==" + currentPosition); -////// if (pagePosition < currentPosition || position == -1) { -////// KLog.e("方向", "左滑"); -////// position = pagePosition; -////// } else { -////// KLog.e("方向", "右滑"); -////// position = pagePosition + 1; -////// } -////// } -//// //state 有三种取值: 0.什么都没做;1.开始滑动;2.滑动结束; 滑动过程的顺序,从滑动开始依次为:(1,2,0) -//// @Override -//// public void onPageScrollStateChanged(int state) { -//// } -// -// -// // 功能:该函数用来判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View) -// // Note: 判断出去的view是否等于进来的view 如果为true直接复用 -// @Override -// public boolean isViewFromObject(View arg0, Object arg1) { -// return arg0 == arg1; -// } -// -// -// // 这个函数的功能是创建指定位置的页面视图 -// // viewpager预加载是这样产生的:在PagerAdapter里的instantiateItem方法中,如果有加载数据的逻辑,则viewpager就会预加载。 -// // 所以加载数据的逻辑不能放在PagerAdapter里的instantiateItem方法中。我们可以将加载数据逻辑放到页面切换监听中。 -// // Note: 做了两件事,第一:将当前视图添加到container中,第二:返回当前View -//// private List mWebViewCaches = new ArrayList<>(); -// -// // 初始化一个 WebView 有以下几个步骤: -// // 1.设置 WebSettings -// // 2.设置 WebViewClient,①重载点击链接的时间,从而控制之后的跳转;②重载页面的开始加载事件和结束加载事件,从而控制图片和点击事件的加载 -// // 3.设置 WebChromeClient,①重载页面的加载改变事件,从而注入bugly的js监控 -// // 4.设置 DownloadListener,从而实现下载文件时使用系统的下载方法 -// // 5.设置 JS通讯 -// @Override -// public Object instantiateItem(ViewGroup container, final int position) { -// final WebViewX webView; -// if (App.i().mWebViewCaches.size() > 0) { -// webView = App.i().mWebViewCaches.get(0); -// App.i().mWebViewCaches.remove(0); -// KLog.e("WebView复用" + webView); -// } else { -// webView = new WebViewX(activity); -// webView.setWebViewClient(new WebViewClientX(activity)); -// KLog.e("WebView创建" + webView); -// } -// webView.setWebViewClient(new WebViewClientX(activity)); -// webView.addJavascriptInterface( activity, "ImageBridge"); -// Tool.setBackgroundColor(webView); -// KLog.e("WebView" + webView); -// // 方便在其他地方调用 viewPager.findViewById 来找到 webView -// webView.setId(position); -// // 方便在webView onPageFinished 的时候调用 -// webView.setTag(dataList.get(position).getId()); -// container.addView(webView); -// -// // TODO: 2018/3/11 检查该订阅源默认显示什么。同一个webview展示的内容有4种模式 -// // 【RSS,已读,保存的网页,原始网页】 -// // 由于是在 ViewPager 中使用 WebView,WebView 的内容要提前加载好,让ViewPager在左右滑的时候可见到左右的界面的内容。(但是这样是不是太耗资源了) -// -// -// // TODO: 2018/3/11 兼容历史数据 -// webView.post(new Runnable() { -// @Override -// public void run() { -//// webView.loadDataWithBaseURL(FileUtil.getAbsoluteDir(dataList.get(position).getSaveDir()), StringUtil.getArticleHeader(dataList.get(position)) + initContent3( dataList.get(position) ) + StringUtil.getArticleFooter(), "text/html", "utf-8", null); -// webView.loadDataWithBaseURL(FileUtil.getAbsoluteDir(dataList.get(position).getSaveDir()), StringUtil.getArticleHeader(dataList.get(position)) + initContent3( dataList.get(position) ) + StringUtil.getArticleFooter()); -// } -// }); -// -// return webView; -// } -// -// private String initContent1(Article article ){ -// return article.getPageForDisplay(); -// } -// -// private String initContent2(Article article ){ -// String articleContent = article.getPageForDisplay(); -// Document document = Jsoup.parseBodyFragment(articleContent); -// Elements elements = document.getElementsByTag("img"); -// -// for(Element element : elements){ -// element.attr("original-src", element.attr("src")); -// element.removeAttr("src"); -// } -// articleContent = document.body().html(); -// return articleContent; -// } -// -// /** -// * 这里没有直接将原始的文章内容给到 webView 加载,再去 webView 中初始化占位图并懒加载。 -// * 是因为这样 WebView 刚启动时,有的图片因为还没有被 js 替换为占位图,而展示一个错误图。 -// * -// * 这里直接将内容初始化好,再让 WebView 执行懒加载的 js 去给没有加载本地图的 src 执行下载任务 -// * @param article -// * @return -// */ -// private String initContent3(Article article ){ -// String cacheUrl; -// String originalUrl; -// String imageHolder; -// if( !HttpUtil.isNetworkAvailable() ){ -// imageHolder = "file:///android_asset/image/image_holder_load_failed.png"; -// KLog.e("ImageBridge", "没有网络"); -// }else if( PrefUtils.i().isDownImgWifi() && !HttpUtil.isWiFiUsed() ){ -// imageHolder = "file:///android_asset/image/image_holder_click_to_load.png"; -// KLog.e("ImageBridge", "开启省流量,蜂窝模式"); -// }else { -// imageHolder = "file:///android_asset/image/image_holder_loading.png"; -// } -// -// String articleContent = article.getPageForDisplay(); -// Document document = Jsoup.parseBodyFragment(articleContent); -// Elements elements = document.getElementsByTag("img"); -// -// for(Element element : elements){ -// originalUrl = element.attr("src"); -// element.attr("original-src", originalUrl); -// cacheUrl = FileUtil.readCacheFilePath(article.getId(),originalUrl); -// if( cacheUrl != null ){ -// element.attr("src", cacheUrl); -// }else { -// element.attr("src", imageHolder); -// } -// } -// articleContent = document.body().html(); -// return articleContent; -// } -// -// // Note: 销毁预加载以外的view对象, 会把需要销毁的对象的索引位置传进来就是position -// @Override -// public void destroyItem(ViewGroup container, int position, Object object) { -// if (object == null) { -// return; -// } -// container.removeView((View) object); -// ((WebViewX) object).clear(); -// App.i().mWebViewCaches.add((WebViewX) object); -// } -// -// @Override -// public int getCount() { -// return (null == dataList) ? 0 : dataList.size(); -// } -// -// -// -//// public void onResume() { -//// if( activity.viewPager.getCurrentItem()){ -//// -//// } -//// EntryView view = mEntryViews.get(mCurrentPagerPos); -//// if (view != null) { -//// view.onResume(); -//// } -//// } -// -// -// public void onPause() { -// for (int i = 0; i < App.i().mWebViewCaches.size(); i++) { -// App.i().mWebViewCaches.get(i).onPause(); -// } -// } -// -//} diff --git a/app/src/main/java/me/wizos/loread/bean/Enclosure.java b/app/src/main/java/me/wizos/loread/bean/Enclosure.java new file mode 100644 index 0000000..0cfe448 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/Enclosure.java @@ -0,0 +1,35 @@ +package me.wizos.loread.bean; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class Enclosure { + @SerializedName(value = "href", alternate = {"content_url"}) + private String href; + // 值有text/html、image/jpeg、application/rss+xml; charset=UTF-8(href是https://justyy.com/feed) + @SerializedName(value = "type", alternate = {"content_type"}) + private String type; + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String toString() { + return "Enclosure [href:" + href + ", type:" + type + "]"; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/LogLevel.java b/app/src/main/java/me/wizos/loread/bean/LogLevel.java new file mode 100644 index 0000000..4802062 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/LogLevel.java @@ -0,0 +1,17 @@ +//package me.wizos.loreadx.bean; +// +//public enum LogLevel { +// debug("debug"),info("info"),warning("warning"),error("error"),none("none"); +// +// private String level; +// +// LogLevel(String level) { } +// +// public String getLevel() { +// return level; +// } +// +// public void setLevel(String level) { +// this.level = level; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/Token.java b/app/src/main/java/me/wizos/loread/bean/Token.java new file mode 100644 index 0000000..d30f50c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/Token.java @@ -0,0 +1,109 @@ +package me.wizos.loread.bean; + +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; +import com.socks.library.KLog; + +/** + * Created by Wizos on 2019/2/17. + */ + +public class Token { + /* + 1、refresh_token也有过期时间,如百度云平台会提供有效期为1个月的Access Token和有效期为10年的Refresh Token, + 2、当refresh_token过期的时候,则需要用户重新授权登录。  + 3、每次登录后,会产生新token,原来的access_token与refresh_token自然失效。 + 4、refresh_token 仅能使用一次,使用一次后,将被废弃。 + 原文:https://blog.csdn.net/lvxiangan/article/details/78020674 + */ + private String access_token; + private String refresh_token; + private String token_type; + // 秒 + private long expires_in; + + @SerializedName("error") + private String error; + @SerializedName("auth") + private String auth; + + +// { +// "access_token": "[ACCESS_TOKEN]", +// "token_type": "Bearer", +// "expires_in": [EXPIRATION_IN_SECONDS], +// "refresh_token": "[REFRESH_TOKEN]", +// "scope": "read" +// } +// { +// "id": "c805fcbf-3acf-4302-a97e-d82f9d7c897f", +// "refresh_token": "AQAA7rJ7InAiOjEsImEiOiJmZWVk...", +// "access_token": "AQAAF4iTvPam_M4_dWheV_5NUL8E...", +// "expires_in": 3920, +// "token_type": "Bearer", +// "plan": "standard", +// "state": "..." +// } + + + public String getAccess_token() { + return access_token; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public String getRefresh_token() { + return refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } + + public String getToken_type() { +// if(!TextUtils.isEmpty(token_type)){ +// token_type = token_type.substring(0, 1).toUpperCase() + token_type.substring(1); +// } + return token_type; + } + + public void setToken_type(String token_type) { + KLog.e("授权吗A:" + token_type); + if (!TextUtils.isEmpty(token_type)) { + token_type = token_type.substring(0, 1).toUpperCase() + token_type.substring(1); + } + this.token_type = token_type; + } + + public long getExpires_in() { + return expires_in; + } + + public void setExpires_in(long refresh_token) { + this.expires_in = expires_in; + } + + public String getAuth() { + return token_type + " " + access_token; + } + + public void setAuth(String auth) { + this.auth = auth; + } + + + @Override + public String toString() { + return "Token{" + + "access_token='" + access_token + '\'' + + ", refresh_token='" + refresh_token + '\'' + + ", token_type='" + token_type + '\'' + + ", Auth='" + getAuth() + '\'' + + ", expires_in=" + expires_in + + ", error='" + error + '\'' + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/config/UserAgent.java b/app/src/main/java/me/wizos/loread/bean/UserAgent.java similarity index 93% rename from app/src/main/java/me/wizos/loread/bean/config/UserAgent.java rename to app/src/main/java/me/wizos/loread/bean/UserAgent.java index 255eb9c..903cfad 100644 --- a/app/src/main/java/me/wizos/loread/bean/config/UserAgent.java +++ b/app/src/main/java/me/wizos/loread/bean/UserAgent.java @@ -1,4 +1,4 @@ -package me.wizos.loread.bean.config; +package me.wizos.loread.bean; /** diff --git a/app/src/main/java/me/wizos/loread/bean/config/FeedConfig.java b/app/src/main/java/me/wizos/loread/bean/config/FeedConfig.java deleted file mode 100644 index 94c20e9..0000000 --- a/app/src/main/java/me/wizos/loread/bean/config/FeedConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.wizos.loread.bean.config; - -/** - * Created by Wizos on 2018/4/11. - */ - -public class FeedConfig { - private String openMode; - private String referer; // // 已废弃,改用GlobalConfig来配置 。 1.auto 2.具体值 - private String userAgent; // 已废弃,改用GlobalConfig来配置 - - public FeedConfig(String openMode, String referer, String userAgent) { - this.openMode = openMode; - this.referer = referer; - this.userAgent = userAgent; - } - - public String getOpenMode() { - return openMode; - } - - public void setOpenMode(String openMode) { - this.openMode = openMode; - } - - @Deprecated - public String getReferer() { - return referer; - } - - @Deprecated - public void setReferer(String referer) { - this.referer = referer; - } - - @Deprecated - public String getUserAgent() { - return userAgent; - } - - @Deprecated - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/config/GlobalConfig.java b/app/src/main/java/me/wizos/loread/bean/config/GlobalConfig.java deleted file mode 100644 index 12e87bc..0000000 --- a/app/src/main/java/me/wizos/loread/bean/config/GlobalConfig.java +++ /dev/null @@ -1,212 +0,0 @@ -package me.wizos.loread.bean.config; - -import android.text.TextUtils; -import android.util.ArrayMap; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.Map; - -import me.wizos.loread.App; -import me.wizos.loread.utils.FileUtil; - -/** - * @author Wizos on 2018/4/14. - */ - -// TODO: 2018/4/14 全局的配置。可用于在启动时连接服务器,获取到搜索的接口? -public class GlobalConfig { - private boolean blockAD = true; - private int userAgentIndex; // 结果为-2 为智能UA(命中规则的走规则,未命中的走默认),-1是默认UA,其他的就是内置的UA - private ArrayList userAgents; - private ArrayMap userAgentsRouter; // 格式是 domain, userAgentIndex - private ArrayMap refererRouter; // 格式是 domain, Referer - private ArrayMap displayRouter; // 格式是 feedId, mode - - - public ArrayList getUserAgents() { - return userAgents; - } - - public void setUserAgents(ArrayList userAgents) { - this.userAgents = userAgents; - } - - public int getUserAgentIndex() { - return userAgentIndex; - } - - public void setUserAgentIndex(int userAgentIndex) { - this.userAgentIndex = userAgentIndex; - } - - public boolean isBlockAD() { - return blockAD; - } - - public void setBlockAD(boolean blockAD) { - this.blockAD = blockAD; - } - - public ArrayMap getUserAgentsRouter() { - return userAgentsRouter; - } - - public void setUserAgentsRouter(ArrayMap userAgentsRouter) { - this.userAgentsRouter = userAgentsRouter; - } - - public ArrayMap getRefererRouter() { - return refererRouter; - } - - public void setRefererRouter(ArrayMap refererRouter) { - this.refererRouter = refererRouter; - } - - public void addRefererRouter(String key, String referer) { - userAgentsRouter.put(key, referer); - } - - public GlobalConfig removeRefererRouter(String key) { - userAgentsRouter.remove(key); - return this; - } - - public ArrayMap getDisplayRouter() { - return displayRouter; - } - - public void setDisplayRouter(ArrayMap displayRouter) { - this.displayRouter = displayRouter; - } - - public void addDisplayRouter(String feedId, String displayMode) { - displayRouter.put(feedId, displayMode); - } - - public void removeDisplayRouter(String key) { - displayRouter.remove(key); - } - - - public String getUserAgentString() { - if (userAgentIndex < 0) { - return ""; - } - return userAgents.get(userAgentIndex).getValue(); - } - - - public String getDisplayMode(String feedId) { - if (TextUtils.isEmpty(feedId)) { - return ""; - } - if (displayRouter.containsKey(feedId)) { - return displayRouter.get(feedId); - } - return ""; - } - - public String guessUserAgentByUrl(String url) { - if (TextUtils.isEmpty(url)) { - return ""; - } - url = url.toLowerCase(Locale.getDefault()); - for (Map.Entry entry : userAgentsRouter.entrySet()) { - if (url.contains(entry.getKey())) { - return entry.getValue(); - } - } - return ""; - } - - /** - * 用于手动下载图片 - * 有3中方法获取referer: - * 1.根据feedid,推断出referer。。优点是简单,但是可能由于rss是第三方烧制的,可能会失效。 - * 2.根据文章url,推断出referer。 - * 2.根据图片url,猜测出referer,配置繁琐、低效,但是适应性较强。(可解决图片用的是第三方服务) - * - * @param url - * @return - */ - public String guessRefererByUrl(String url) { - if (TextUtils.isEmpty(url)) { - return ""; - } - url = url.toLowerCase(Locale.getDefault()); - for (Map.Entry entry : refererRouter.entrySet()) { - if (url.contains(entry.getKey())) { - return entry.getValue(); - } - } -// String domain; -// try { -// URI uri = new URI(url); -// domain = uri.getHost(); -// if (domain == null) { -// int index = url.indexOf('/', 8); // -> http://(7) and https://(8) -// if (index != -1) { -// domain = url.substring(0, index); -// } -// } -// }catch (URISyntaxException u){ -// return ""; -// } -// if(refererRouter.containsKey(domain)){ -// return refererRouter.get(domain); -// } - return ""; - } - - private static GlobalConfig globalConfig; - - private GlobalConfig() { - } - - public static GlobalConfig i() { - if (globalConfig == null) { - synchronized (GlobalConfig.class) { - if (globalConfig == null) { - globalConfig = new GlobalConfig(); - Gson gson = new Gson(); - String config; - config = FileUtil.readFile(App.i().getExternalFilesDir(null) + "/config/global-config.json"); - if (TextUtils.isEmpty(config)) { - ArrayList userAgents = new ArrayList<>(2); - userAgents.add(new UserAgent("iPhone", "Mozilla/5.0 (iPhone; CPU iPhone OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1")); -// userAgents.add(new UserAgent("Android", "Mozilla/5.0 (Linux; Android 5.1; zh-cn; MX5 Build/LMY47I) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1")); - userAgents.add(new UserAgent("Android", "Mozilla/5.0 (Linux; Android 5.1; MX5 Build/LMY47I) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/604.1")); - userAgents.add(new UserAgent("Chrome(PC)", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")); - globalConfig.setUserAgents(userAgents); - globalConfig.setUserAgentIndex(-1); - globalConfig.setUserAgentsRouter(new ArrayMap()); - globalConfig.setRefererRouter(new ArrayMap()); - globalConfig.setDisplayRouter(new ArrayMap()); - globalConfig.setBlockAD(true); - globalConfig.save(); - } else { - globalConfig = gson.fromJson(config, new TypeToken() { - }.getType()); - } - } - } - } - return globalConfig; - } - - - public void save() { - FileUtil.save(App.i().getExternalFilesDir(null) + "/config/global-config.json", new GsonBuilder().setPrettyPrinting().create().toJson(globalConfig)); - } - - public void reInit() { - globalConfig = null; - } - -} diff --git a/app/src/main/java/me/wizos/loread/bean/domain/OutFeed.java b/app/src/main/java/me/wizos/loread/bean/domain/OutFeed.java new file mode 100644 index 0000000..5f529ca --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/domain/OutFeed.java @@ -0,0 +1,50 @@ +package me.wizos.loread.bean.domain; + +/** + * Created by Wizos on 2019/5/15. + */ + +public class OutFeed { + private String title; + private String feedUrl; + private String htmlUrl; + + public OutFeed(String title, String feedUrl, String htmlUrl) { + this.title = title; + this.feedUrl = feedUrl; + this.htmlUrl = htmlUrl; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getFeedUrl() { + return feedUrl; + } + + public void setFeedUrl(String feedUrl) { + this.feedUrl = feedUrl; + } + + public String getHtmlUrl() { + return htmlUrl; + } + + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; + } + + @Override + public String toString() { + return "OutFeed{" + + "title='" + title + '\'' + + ", feedUrl='" + feedUrl + '\'' + + ", htmlUrl='" + htmlUrl + '\'' + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/domain/OutTag.java b/app/src/main/java/me/wizos/loread/bean/domain/OutTag.java new file mode 100644 index 0000000..00ddf82 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/domain/OutTag.java @@ -0,0 +1,45 @@ +package me.wizos.loread.bean.domain; + +import java.util.ArrayList; + +/** + * Created by Wizos on 2019/5/15. + */ + +public class OutTag { + private String title; + private ArrayList outFeeds; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public ArrayList getOutFeeds() { + return outFeeds; + } + + public void setOutFeeds(ArrayList outFeeds) { + this.outFeeds = outFeeds; + } + + public OutTag addOutFeed(OutFeed outFeed) { + if (outFeeds == null) { + outFeeds = new ArrayList<>(); + } + outFeeds.add(outFeed); + return this; + } + + + @Override + public String toString() { + return "OutTag{" + + "title='" + title + '\'' + + ", outFeeds=" + outFeeds + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/CategoryItem.java b/app/src/main/java/me/wizos/loread/bean/feedly/CategoryItem.java new file mode 100644 index 0000000..d315240 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/CategoryItem.java @@ -0,0 +1,41 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +import me.wizos.loread.db.Category; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class CategoryItem { + @SerializedName("id") + private String id; + + @SerializedName("label") + private String label; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + + public Category convert() { + Category category = new Category(); + category.setId(id); + category.setTitle(label); + return category; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Collection.java b/app/src/main/java/me/wizos/loread/bean/feedly/Collection.java new file mode 100644 index 0000000..edfb3a7 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Collection.java @@ -0,0 +1,110 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +import me.wizos.loread.App; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.Feed; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class Collection { + @SerializedName("id") + private String id; + + @SerializedName("label") + private String label; + + @SerializedName("customizable") + private boolean customizable; + + @SerializedName("enterprise") + private boolean enterprise; + + @SerializedName("numFeeds") + private int numFeeds; + + @SerializedName("feeds") + private ArrayList feedItems; + + // 可选,大部分时候没有 +// String description; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public boolean isCustomizable() { + return customizable; + } + + public void setCustomizable(boolean customizable) { + this.customizable = customizable; + } + + public boolean isEnterprise() { + return enterprise; + } + + public void setEnterprise(boolean enterprise) { + this.enterprise = enterprise; + } + + public int getNumFeeds() { + return numFeeds; + } + + public void setNumFeeds(int numFeeds) { + this.numFeeds = numFeeds; + } + + public ArrayList getFeedItems() { + return feedItems; + } + + public void setFeedItems(ArrayList feedItems) { + this.feedItems = feedItems; + } + + + public CategoryItem getCategoryItem() { + CategoryItem categoryItem = new CategoryItem(); + categoryItem.setId(id); + categoryItem.setLabel(label); + return categoryItem; + } + + public Category getCategory() { + Category category = new Category(); + category.setId(id); + category.setTitle(label); + return category; + } + + public ArrayList getFeeds() { + ArrayList feeds = new ArrayList<>(feedItems.size()); + Feed feed; + for (FeedItem feedItem : feedItems) { + feed = feedItem.convert2Feed(); + feed.setUid(App.i().getUser().getId()); + feeds.add(feed); + } + return feeds; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/ContentDirection.java b/app/src/main/java/me/wizos/loread/bean/feedly/ContentDirection.java new file mode 100644 index 0000000..6938d8f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/ContentDirection.java @@ -0,0 +1,30 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class ContentDirection { + @SerializedName("content") + String content; + @SerializedName("direction") + String direction; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Counts.java b/app/src/main/java/me/wizos/loread/bean/feedly/Counts.java new file mode 100644 index 0000000..4ba1e24 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Counts.java @@ -0,0 +1,32 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class Counts { + @SerializedName("unreadcounts") + private ArrayList unreadcounts; + @SerializedName("updated") + private long updated; + + public ArrayList getUnreadcounts() { + return unreadcounts; + } + + public void setUnreadcounts(ArrayList unreadcounts) { + this.unreadcounts = unreadcounts; + } + + public long getUpdated() { + return updated; + } + + public void setUpdated(long updated) { + this.updated = updated; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Entry.java b/app/src/main/java/me/wizos/loread/bean/feedly/Entry.java new file mode 100644 index 0000000..6ab26a0 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Entry.java @@ -0,0 +1,345 @@ +package me.wizos.loread.bean.feedly; + +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +import me.wizos.loread.App; +import me.wizos.loread.bean.Enclosure; +import me.wizos.loread.db.Article; +import me.wizos.loread.network.api.BaseApi; +import me.wizos.loread.utils.ArticleUtil; + +/** + * @author Wizos on 2019/2/8. + */ + +public class Entry { + /** + * 文章的唯一的、不可变的ID。 + */ + private String id; + /** + * 在 RSS feed 中,这篇文章的唯一id (不一定是URL!也可能是文本数字) + */ + private String originId; + /** + * 文章指纹。如果文章被更新,这个值可能会改变。 + */ + private String fingerprint; + /** + * 可选。文章的标题。此字符串不包含任何HTML标记。 + */ + private String title; + + /** + * 可选。文章作者的名字 + */ + private String author; + + /** + * 可选。文章内容的 object 。这个对象通常有两个值:“content”代表内容本身,“direction”(“ltr”代表从左到右,“rtl”代表从右到左)。内容本身包含经过净化的HTML标记。 + */ + private ContentDirection content; + + /** + * 可选。文章概要的 object 。这个对象通常有两个值:“content”代表内容本身,“direction”(“ltr”代表从左到右,“rtl”代表从右到左)。内容本身包含经过净化的HTML标记。 + */ + private ContentDirection summary; + + /** + * 这篇文章发表时的时间戳,单位为毫秒(通常不准确)。 + */ + private long published; + /** + * 可空。时间戳。这篇文章更新时的时间戳,单位为毫秒 + */ + private long updated; + + /** + * 时间戳,单位为毫秒。当这篇文章被feedly Cloud服务器处理时,不可变的时间戳。 + */ + @SerializedName("crawled") + private long crawled; + +// /** +// * 可空。时间戳,以毫秒为单位。这篇文章被feedly Cloud服务器重新处理和更新时的时间戳。 +// */ +// private long recrawled; + /** + * 可空。origin对象是本文的爬取源。如果存在,“streamId”将包含feedId,“title”将包含feedTitle,“htmlUrl”将包含提要的网站。 + */ + private Origin origin; + + // 可能为空 + private String canonicalUrl; + +// // 可能为空,不知用处 +// @SerializedName("ampUrl") +// private String ampUrl; +// +// // 可能为空,不知用处 +// @SerializedName("cdnAmpUrl") +// private String cdnAmpUrl; + + /** + * 可空。链接对象数组。本文的原始(?)链接列表。 + */ + private ArrayList canonical; + + /** + * 可空。链接对象数组。本文的替代链接列表。每个链接对象包含一种媒体类型和一个URL。通常,存在单个对象,其中包含到原始网页的链接。 + */ + private ArrayList alternate; + + /** + * 可空。由feed提供的的媒体链接列表(视频、图像、声音等)。有些条目没有摘要或内容,只有媒体链接的集合。 + */ + private ArrayList enclosure; + +// /** +// * 视觉对象。这个条目的图像URL。如果存在,“url”将包含图像URL,“宽度”和“高度”其维度,“内容类型”其MIME类型。 +// */ +// private Visual visual; + + private ArrayList keywords; + + /** + * 这个条目被用户读取了吗?如果 header 中未提供 Authorization,这将始终返回false。如果提供了,它将反映用户是否读过该条目。 + */ + private boolean unread; + +// /** +// * 貌似仅出现在 StreamContents 接口中 +// */ +// @SerializedName("categories") +// private ArrayList categories; +// @SerializedName("tags") +// private ArrayList tags; + + /* + * 其他可能的字段 + * + "recrawled": 1549551157946, + "updateCount": 1, + + "Visual": { + "url": "none" + }, + "unread": false, + "categories": [ + { + "id": "user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/category/1_博谈", + "label": "1_博谈" + } + ], + "tags": [ + { + "id": "user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/tag/global.read", + "label": "" + } + ], + // 表明这个条目有多受欢迎。这个数字越高,越多的读者阅读、保存或分享了这个条目。 + "engagement": 15, + "engagementRate": 15 + + "thumbnail": [ + { + "url": "https://2.bp.blogspot.com/-Jdp88f-UJ5c/XHFxq_pvD7I/AAAAAAAADHI/4z0axErIqq0FAgbIaTlveAodRWBf2EfkACK4BGAYYCw/s72-c/usa-dollar.jpg", + "width": 72, + "height": 72 + } + ], + + 手动添加的文章,通过“/entries/.mget”接口会包含以下字段 + + "createdBy": { + "userAgent": "PostmanRuntime/7.4.0", + "application": "Feedly Developer" + }, + "canonicalUrl": "https://www.theverge.com/2013/4/17/4236096/nbc-heroes-may-get-a-second-lease-on-life-on-xbox-live", + "ampUrl": "https://www.theverge.com/platform/amp/2013/4/17/4236096/nbc-heroes-may-get-a-second-lease-on-life-on-xbox-live", + "cdnAmpUrl": "https://www-theverge-com.cdn.ampproject.org/c/s/www.theverge.com/platform/amp/2013/4/17/4236096/nbc-heroes-may-get-a-second-lease-on-life-on-xbox-live", + + */ + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getOriginId() { + return originId; + } + + public void setOriginId(String originId) { + this.originId = originId; + } + + public String getFingerprint() { + return fingerprint; + } + + public void setFingerprint(String fingerprint) { + this.fingerprint = fingerprint; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public ContentDirection getContent() { + return content; + } + + public void setContent(ContentDirection content) { + this.content = content; + } + + public ContentDirection getSummary() { + return summary; + } + + public void setSummary(ContentDirection summary) { + this.summary = summary; + } + + public long getPublished() { + return published; + } + + public void setPublished(long published) { + this.published = published; + } + + public long getCrawled() { + return crawled; + } + + public void setCrawled(long crawled) { + this.crawled = crawled; + } + + public long getUpdated() { + return updated; + } + + public void setUpdated(long updated) { + this.updated = updated; + } + + public Origin getOrigin() { + return origin; + } + + public void setOrigin(Origin origin) { + this.origin = origin; + } + + public String getCanonicalUrl() { + return canonicalUrl; + } + + public void setCanonicalUrl(String canonicalUrl) { + this.canonicalUrl = canonicalUrl; + } + + public ArrayList getCanonical() { + return canonical; + } + + public void setCanonical(ArrayList canonical) { + this.canonical = canonical; + } + + public ArrayList getAlternate() { + return alternate; + } + + public void setAlternate(ArrayList alternate) { + this.alternate = alternate; + } + + public ArrayList getEnclosure() { + return enclosure; + } + + public void setEnclosure(ArrayList enclosure) { + this.enclosure = enclosure; + } + + public ArrayList getKeywords() { + return keywords; + } + + public void setKeywords(ArrayList keywords) { + this.keywords = keywords; + } + + public boolean isUnread() { + return unread; + } + + public void setUnread(boolean unread) { + this.unread = unread; + } + + public Article convert(BaseApi.ArticleChanger articleChanger) { + Article article = new Article(); + article.setId(id); + + title = ArticleUtil.getOptimizedTitle(title); + article.setTitle(title); + + article.setAuthor(author); + article.setPubDate(published); + + if (alternate != null && alternate.size() > 0) { + article.setLink(alternate.get(0).getHref()); + } + if (origin != null) { + article.setFeedId(origin.getStreamId()); + article.setFeedTitle(origin.getTitle()); + } + + String tmpContent = ""; + if (content != null && !TextUtils.isEmpty(content.getContent())) { + tmpContent = ArticleUtil.getOptimizedContent(article.getLink(), content.getContent()); + } else if (summary != null && !TextUtils.isEmpty(summary.getContent())) { + tmpContent = ArticleUtil.getOptimizedContent(article.getLink(), summary.getContent()); + } + tmpContent = ArticleUtil.getOptimizedContentWithEnclosures(tmpContent,enclosure); + article.setContent(tmpContent); + + String tmpSummary = ArticleUtil.getOptimizedSummary(tmpContent); + article.setSummary(tmpSummary); + + String coverUrl = ArticleUtil.getCoverUrl(article.getLink(),tmpContent); + article.setImage(coverUrl); + + // 自己设置的字段 + // KLog.i("【增加文章】" + article.getId()); + article.setSaveStatus(App.STATUS_NOT_FILED); + if (articleChanger != null) { + articleChanger.change(article); + } + return article; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/FeedItem.java b/app/src/main/java/me/wizos/loread/bean/feedly/FeedItem.java new file mode 100644 index 0000000..b043aa5 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/FeedItem.java @@ -0,0 +1,175 @@ +package me.wizos.loread.bean.feedly; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + +import me.wizos.loread.App; +import me.wizos.loread.db.Feed; + +/** + * 以搜索知乎,feed/http://zhihurss.miantiao.me/section/id/2 源为例 + * Created by Wizos on 2019/2/8. + */ + +public class FeedItem { + private String id; + private String feedId; + private String title; + private String description; + private String website; + private String iconUrl; // 可能为空,小图 + private String visualUrl; // 可能为空,大图 + private String language; // 值可能为:zh,en + private int subscribers; + private long updated; + private float velocity; // 每周发布的文章的平均数量。 此号码每隔几天更新一次 + private boolean partial; // 可能为空;部分的; 偏爱的 + private String contentType; // 可能为空;可能为 article, longform + private String state; // 可能为空。值可能为:dead.stale,dormant + private ArrayList topics; // 可能为空 + + // 单独获取feed信息时可见(批量接口) + // private int estimatedEngagement; + + // 搜索时可见 + // private long lastUpdated; // 可能用不到吧,和 Updated 字段类似 + // private float score; + // private int coverage; + // private int coverageScore; + // private int averageReadTime; + // private String websiteTitle; + // private int totalTagCount; + // private ArrayList<> tagCounts; + // private ArrayList deliciousTags; + + // 以下不常见到 + // String coverColor; + // String logo; + // String relatedLayout; + // String relatedTarget; + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getFeedId() { + return feedId; + } + public void setFeedId(String feedId) { + this.feedId = feedId; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getWebsite() { + return website; + } + public void setWebsite(String website) { + this.website = website; + } + public String getIconUrl() { + return iconUrl; + } + public void setIconUrl(String iconUrl) { + this.iconUrl = iconUrl; + } + public String getVisualUrl() { + return visualUrl; + } + public void setVisualUrl(String visualUrl) { + this.visualUrl = visualUrl; + } + public String getLanguage() { + return language; + } + public void setLanguage(String language) { + this.language = language; + } + public int getSubscribers() { + return subscribers; + } + public void setSubscribers(int subscribers) { + this.subscribers = subscribers; + } + public long getUpdated() { + return updated; + } + public void setUpdated(long updated) { + this.updated = updated; + } + public float getVelocity() { + return velocity; + } + public void setVelocity(float velocity) { + this.velocity = velocity; + } + public ArrayList getTopics() { + return topics; + } + public void setTopics(ArrayList topics) { + this.topics = topics; + } + public boolean isPartial() { + return partial; + } + public void setPartial(boolean partial) { + this.partial = partial; + } + public String getContentType() { + return contentType; + } + public void setContentType(String contentType) { + this.contentType = contentType; + } + public String getState() { + return state; + } + public void setState(String state) { + this.state = state; + } + + public Feed convert2Feed() { + Feed feed = new Feed(); + feed.setId(id); + feed.setTitle(title); + feed.setFeedUrl(id.substring(5)); + feed.setHtmlUrl(website); + feed.setIconUrl(visualUrl); + feed.setDisplayMode(App.OPEN_MODE_RSS); + return feed; + } + + @NotNull + @Override + public String toString() { + return "TTRSSFeedItem{" + + "id='" + id + '\'' + + ", feedId='" + feedId + '\'' + + ", title='" + title + '\'' + + ", description='" + description + '\'' + + ", website='" + website + '\'' + + ", iconUrl='" + iconUrl + '\'' + + ", visualUrl='" + visualUrl + '\'' + + ", language='" + language + '\'' + + ", subscribers=" + subscribers + + ", updated=" + updated + + ", velocity=" + velocity + + ", partial=" + partial + + ", contentType='" + contentType + '\'' + + ", state='" + state + '\'' + + ", topics=" + topics + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Origin.java b/app/src/main/java/me/wizos/loread/bean/feedly/Origin.java new file mode 100644 index 0000000..914ec96 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Origin.java @@ -0,0 +1,40 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class Origin { + @SerializedName("streamId") + String streamId; + @SerializedName("title") + String title; + @SerializedName("htmlUrl") + String htmlUrl; + + public String getStreamId() { + return streamId; + } + + public void setStreamId(String streamId) { + this.streamId = streamId; + } + + public String getHtmlUrl() { + return htmlUrl; + } + + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Profile.java b/app/src/main/java/me/wizos/loread/bean/feedly/Profile.java new file mode 100644 index 0000000..752e946 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Profile.java @@ -0,0 +1,94 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +import me.wizos.loread.Contract; +import me.wizos.loread.db.User; + +/** + * @author Wizos on 2019/2/11. + */ + +public class Profile { + + /* + "id": "12cc057f-9891-4ab3-99da-86f2dee7f2f5", + "client": "feedly", + "created": 1457934974286, + "email": "wizos@qq.com", + "wave": "2016.12", + "verified": true, + "login": "wizos@qq.com", + "logins": [ + { + "id": "wizos@qq.com", + "verified": false, + "provider": "FeedlyLogin", + "providerId": "wizos@qq.com" + } + ], + "refPage": "welcome", + "landingPage": "welcome", + "dropboxConnected": false, + "twitterConnected": false, + "facebookConnected": false, + "evernoteConnected": false, + "pocketConnected": false, + "wordPressConnected": false, + "windowsLiveConnected": false, + "instapaperConnected": false, + "source": "feedly.desktop 30.0.1117", + "fullName": "wizos" + */ + + @SerializedName("id") + private String id; + @SerializedName("login") + private String login; + @SerializedName("email") + private String email; + @SerializedName("fullName") + private String fullName; + + public User getUser() { + User user = new User(); + user.setSource(Contract.PROVIDER_FEEDLY); + user.setId(Contract.PROVIDER_FEEDLY + "_" + id); + user.setUserId(id); + user.setUserEmail(email); + user.setUserName(fullName); + return user; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/StreamContents.java b/app/src/main/java/me/wizos/loread/bean/feedly/StreamContents.java new file mode 100644 index 0000000..281f7b1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/StreamContents.java @@ -0,0 +1,47 @@ +package me.wizos.loread.bean.feedly; + +import java.util.ArrayList; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class StreamContents { + private String id; + private String title; + private long updated; + private String continuation; + private ArrayList items; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public long getUpdated() { + return updated; + } + + public void setUpdated(long updated) { + this.updated = updated; + } + + public String getContinuation() { + return continuation; + } + + public void setContinuation(String continuation) { + this.continuation = continuation; + } + + public ArrayList getItems() { + return items; + } + + public void setItems(ArrayList items) { + this.items = items; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/StreamIds.java b/app/src/main/java/me/wizos/loread/bean/feedly/StreamIds.java new file mode 100644 index 0000000..40d1147 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/StreamIds.java @@ -0,0 +1,33 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class StreamIds { + @SerializedName("ids") + private ArrayList ids; + + @SerializedName("continuation") + private String continuation; + + public ArrayList getIds() { + return ids; + } + + public void setIds(ArrayList ids) { + this.ids = ids; + } + + public String getContinuation() { + return continuation; + } + + public void setContinuation(String continuation) { + this.continuation = continuation; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Subscription.java b/app/src/main/java/me/wizos/loread/bean/feedly/Subscription.java new file mode 100644 index 0000000..5ad5d34 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Subscription.java @@ -0,0 +1,187 @@ +//package me.wizos.loreadx.bean.feedly; +// +//import com.google.gson.annotations.SerializedName; +// +//import java.util.ArrayList; +// +///** +// * Created by Wizos on 2019/2/8. +// */ +// +//public class Subscription { +// private String id; +// private String title; +// private String website; +// // 可能为空 +// private String iconUrl; +// // 可能为空 +// private String visualUrl; +// private String description; +// +// // 值可能为:zh,en +// private String language; +// private int subscribers; +// private long updated; +// private float velocity; +// // 可能为空;部分的; 偏爱的 +// private boolean partial; +// // 可能为空;可能为 article, longform +// private String contentType; +// // 可能为空。值可能为:dead.stale,dormant +// private String state; +// // 可能为空 +// private ArrayList topics; +// +// private ArrayList categories; +// +// // estimatedEngagement +// // coverUrl +// // logo +// // coverColor +// // relatedLayout +// // relatedTarget +// +// +// +// +// public String getId() { +// return id; +// } +// +// public void setId(String id) { +// this.id = id; +// } +// +// public String getTitle() { +// return title; +// } +// +// public void setTitle(String title) { +// this.title = title; +// } +// +// public String getWebsite() { +// return website; +// } +// +// public void setWebsite(String website) { +// this.website = website; +// } +// +// public String getIconUrl() { +// return iconUrl; +// } +// +// public void setIconUrl(String iconUrl) { +// this.iconUrl = iconUrl; +// } +// +// public String getVisualUrl() { +// return visualUrl; +// } +// +// public void setVisualUrl(String visualUrl) { +// this.visualUrl = visualUrl; +// } +// +// public int getSubscribers() { +// return subscribers; +// } +// +// public void setSubscribers(int subscribers) { +// this.subscribers = subscribers; +// } +// +// public long getUpdated() { +// return updated; +// } +// +// public void setUpdated(long updated) { +// this.updated = updated; +// } +// +// public float getVelocity() { +// return velocity; +// } +// +// public void setVelocity(float velocity) { +// this.velocity = velocity; +// } +// +// public boolean isPartial() { +// return partial; +// } +// +// public void setPartial(boolean partial) { +// this.partial = partial; +// } +// +// public String getContentType() { +// return contentType; +// } +// +// public void setContentType(String contentType) { +// this.contentType = contentType; +// } +// +// public String getState() { +// return state; +// } +// +// public void setState(String state) { +// this.state = state; +// } +// +// public ArrayList getTopics() { +// return topics; +// } +// +// public void setTopics(ArrayList topics) { +// this.topics = topics; +// } +// +// public ArrayList getCategoryItems() { +// return categories; +// } +// +// public void setCategoryItems(ArrayList categories) { +// this.categories = categories; +// } +// +// public String getDescription() { +// return description; +// } +// +// public void setDescription(String description) { +// this.description = description; +// } +// +// public String getLanguage() { +// return language; +// } +// +// public void setLanguage(String language) { +// this.language = language; +// } +// +// @Override +// public String toString() { +// return "Subscription{" + +// "id='" + id + '\'' + +// ", title='" + title + '\'' + +// ", website='" + website + '\'' + +// ", iconUrl='" + iconUrl + '\'' + +// ", visualUrl='" + visualUrl + '\'' + +// ", description='" + description + '\'' + +// ", language='" + language + '\'' + +// ", subscribers=" + subscribers + +// ", updated=" + updated + +// ", velocity=" + velocity + +// ", partial=" + partial + +// ", contentType='" + contentType + '\'' + +// ", state='" + state + '\'' + +// ", topics=" + topics + +// ", categories=" + categories + +// '}'; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Unreadcount.java b/app/src/main/java/me/wizos/loread/bean/feedly/Unreadcount.java new file mode 100644 index 0000000..60d7934 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Unreadcount.java @@ -0,0 +1,40 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class Unreadcount { + @SerializedName("id") + private String id; + @SerializedName("count") + private int count; + @SerializedName("updated") + private long updated; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public long getUpdated() { + return updated; + } + + public void setUpdated(long updated) { + this.updated = updated; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/Visual.java b/app/src/main/java/me/wizos/loread/bean/feedly/Visual.java new file mode 100644 index 0000000..eb8172b --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/Visual.java @@ -0,0 +1,65 @@ +package me.wizos.loread.bean.feedly; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by Wizos on 2019/2/24. + */ + +public class Visual { + /** + * 可能为none + */ + @SerializedName("url") + private Origin url; + + /** + * 可空(可能性中) + */ + @SerializedName("contentType") + private Origin contentType; + + /** + * 可空(可能性高) + */ + @SerializedName("width") + private int width; + + /** + * 可空(可能性高) + */ + @SerializedName("height") + private int height; + + public Origin getUrl() { + return url; + } + + public void setUrl(Origin url) { + this.url = url; + } + + public Origin getContentType() { + return contentType; + } + + public void setContentType(Origin contentType) { + this.contentType = contentType; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/input/EditCollection.java b/app/src/main/java/me/wizos/loread/bean/feedly/input/EditCollection.java new file mode 100644 index 0000000..bfdc02b --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/input/EditCollection.java @@ -0,0 +1,69 @@ +package me.wizos.loread.bean.feedly.input; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +/** + * Created by Wizos on 2019/2/24. + */ + +public class EditCollection { + /** + * 此集合的唯一标签;新类别所需,编辑现有类别时可选 + */ + @SerializedName("label") + private String label; + /** + * 可空。Collection的标识。如果缺失,服务器将生成一个(新集合Collection)。 + */ + @SerializedName("id") + private String id; + /** + * 这个集合的更详细的描述。 + */ + @SerializedName("description") + private String description; + /** + * 可空。feed列表,表示要添加到此集合的feed列表。 + */ + // 必须没有categories + @SerializedName("feeds") + private ArrayList feeds; + + public EditCollection(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public ArrayList getFeeds() { + return feeds; + } + + public void setFeeds(ArrayList feeds) { + this.feeds = feeds; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/input/EditFeed.java b/app/src/main/java/me/wizos/loread/bean/feedly/input/EditFeed.java new file mode 100644 index 0000000..9a98622 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/input/EditFeed.java @@ -0,0 +1,69 @@ +package me.wizos.loread.bean.feedly.input; + +import java.util.ArrayList; +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.bean.feedly.CategoryItem; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; + +/** + * Created by Wizos on 2019/2/24. + */ + +public class EditFeed { + private String id; + private String title; + private ArrayList categoryItems = new ArrayList<>(); + + public EditFeed() { + } + + + public EditFeed(String feedId) { + Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), feedId); + id = feed.getId(); + title = feed.getTitle(); + List categories = CoreDB.i().categoryDao().getByFeedId(App.i().getUser().getId(), feedId); + for (Category category : categories) { + categoryItems.add(category.convert2CategoryItem()); + } + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public ArrayList getCategoryItems() { + return categoryItems; + } + + public void setCategoryItems(ArrayList categoryItems) { + this.categoryItems = categoryItems; + } + + + @Override + public String toString() { + return "EditFeed{" + + "id='" + id + '\'' + + ", title='" + title + '\'' + + ", categoryItems=" + categoryItems + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/feedly/input/MarkerAction.java b/app/src/main/java/me/wizos/loread/bean/feedly/input/MarkerAction.java new file mode 100644 index 0000000..ef18f9a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/feedly/input/MarkerAction.java @@ -0,0 +1,89 @@ +package me.wizos.loread.bean.feedly.input; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Created by Wizos on 2019/2/24. + */ + +public class MarkerAction { + public final static String MARK_AS_READ = "markAsRead"; + public final static String MARK_AS_UNREAD = "keepUnread"; + public final static String UNDO_MARK_AS_READ = "undoMarkAsRead"; + public final static String MARK_AS_SAVED = "markAsSaved"; + public final static String MARK_AS_UNSAVED = "markAsUnsaved"; + + public final static String TYPE_ENTRIES = "entries"; + public final static String TYPE_FEEDS = "feeds"; + public final static String TYPE_CATEGORIES = "categories"; + public final static String TYPE_TAGS = "tags"; + + /** + * markAsRead,keepUnread,undoMarkAsRead,markAsSaved,markAsUnsaved + */ + @SerializedName("action") + private String action; + /** + * entries,feeds,categories,tags + */ + @SerializedName("type") + private String type; + + @SerializedName("entryIds") + private List entryIds; + + @SerializedName("feedIds") + private List feedIds; + + @SerializedName("categoryIds") + private List categoryIds; + +// @SerializedName("lastReadEntryId") +// private String lastReadEntryId; +// +// // 时间戳替代(不太准确) +// @SerializedName("asOf") +// private long asOf; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getEntryIds() { + return entryIds; + } + + public void setEntryIds(List entryIds) { + this.entryIds = entryIds; + } + + public List getFeedIds() { + return feedIds; + } + + public void setFeedIds(List feedIds) { + this.feedIds = feedIds; + } + + public List getCategoryIds() { + return categoryIds; + } + + public void setCategoryIds(List categoryIds) { + this.categoryIds = categoryIds; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/BaseResponse.java b/app/src/main/java/me/wizos/loread/bean/fever/BaseResponse.java new file mode 100644 index 0000000..d16e1ee --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/BaseResponse.java @@ -0,0 +1,31 @@ +package me.wizos.loread.bean.fever; + +import com.google.gson.annotations.SerializedName; + +public class BaseResponse { + @SerializedName("api_version") + private int apiVersion; + @SerializedName("auth") + private int auth; // 为 1 时,代表验证/授权成功 + @SerializedName("last_refreshed_on_time") + private long lastRefreshedOnTime; + @SerializedName("error") + private String error; // NOT_LOGGED_IN + + public int getApiVersion() { + return apiVersion; + } + + public int getAuth() { + return auth; + } + + public long getLastRefreshedOnTime() { + return lastRefreshedOnTime; + } + + + public boolean isSuccessful(){ + return auth == 1; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/Feed.java b/app/src/main/java/me/wizos/loread/bean/fever/Feed.java new file mode 100644 index 0000000..6976e46 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/Feed.java @@ -0,0 +1,62 @@ +//package me.wizos.loreadx.bean.fever; +// +//import com.google.gson.annotations.SerializedName; +// +//import me.wizos.loreadx.App; +// +//public class Feed { +// @SerializedName("id") +// private int id; +// @SerializedName("favicon_id") +// private int faviconId; +// @SerializedName("title") +// private String title; +// @SerializedName("url") +// private String url; +// @SerializedName("site_url") +// private String siteUrl; +// @SerializedName("is_spark") +// private int isSpark; +// @SerializedName("last_updated_on_time") +// private long lastUpdatedOnTime; // 到秒 +// +// public int getId() { +// return id; +// } +// +// public int getFaviconId() { +// return faviconId; +// } +// +// public String getTitle() { +// return title; +// } +// +// public String getUrl() { +// return url; +// } +// +// public String getSiteUrl() { +// return siteUrl; +// } +// +// public int getIsSpark() { +// return isSpark; +// } +// +// public long getLastUpdatedOnTime() { +// return lastUpdatedOnTime; +// } +// +// +// public Feed convert(){ +// Feed feed = new Feed(); +// feed.setId("feed/" + id); +// feed.setTitle(title); +// feed.setFeedUrl(url); +// feed.setHtmlUrl(siteUrl); +// //feed.setIconUrl(visualUrl); +// feed.setOpenMode(App.OPEN_MODE_RSS); +// return feed; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/Feeds.java b/app/src/main/java/me/wizos/loread/bean/fever/Feeds.java new file mode 100644 index 0000000..2c5380e --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/Feeds.java @@ -0,0 +1,20 @@ +//package me.wizos.loreadx.bean.fever; +// +//import com.google.gson.annotations.SerializedName; +// +//import java.util.List; +// +//public class Feeds extends BaseResponse { +// @SerializedName("feeds") +// private List feeds; +// @SerializedName("feeds_groups") +// private List feedsGroups; +// +// public List getFeeds() { +// return feeds; +// } +// +// public List getFeedsGroups() { +// return feedsGroups; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/Group.java b/app/src/main/java/me/wizos/loread/bean/fever/Group.java new file mode 100644 index 0000000..c7fdce5 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/Group.java @@ -0,0 +1,38 @@ +package me.wizos.loread.bean.fever; + +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; + +import me.wizos.loread.db.Category; + +public class Group { + private int id; + private String title; + @SerializedName("feed_ids") + private String feedIds; + + public int getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String[] getFeedIds(){ + if(TextUtils.isEmpty(feedIds)){ + return null; + }else { + return feedIds.split(","); + } + } + + public Category getCategry(){ + Category category = new Category(); + category.setId("user/" + id); + category.setTitle(title); + return category; + } + +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/GroupFeeds.java b/app/src/main/java/me/wizos/loread/bean/fever/GroupFeeds.java new file mode 100644 index 0000000..68bc428 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/GroupFeeds.java @@ -0,0 +1,18 @@ +package me.wizos.loread.bean.fever; + +import com.google.gson.annotations.SerializedName; + +public class GroupFeeds { + @SerializedName("group_id") + private int groupId; + @SerializedName("feed_ids") + private String feedIds; + + public int getGroupId() { + return groupId; + } + + public String getFeedIds() { + return feedIds; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/Groups.java b/app/src/main/java/me/wizos/loread/bean/fever/Groups.java new file mode 100644 index 0000000..185a87a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/Groups.java @@ -0,0 +1,27 @@ +package me.wizos.loread.bean.fever; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * 超级组 Kindling 不在该响应中,它由所有 is_spark = 0 的 feed 组成 + * 超级组 Sparks 不在该响应中,它由所有 is_spark = 1 的 feed 组成 + */ + +public class Groups extends BaseResponse { + @SerializedName("groups") + private List groups; + + @SerializedName("feeds_groups") + private List feedsGroups; + + public List getGroups() { + return groups; + } + + public List getFeedsGroups() { + return feedsGroups; + } + +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/Item.java b/app/src/main/java/me/wizos/loread/bean/fever/Item.java new file mode 100644 index 0000000..f3dca02 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/Item.java @@ -0,0 +1,63 @@ +package me.wizos.loread.bean.fever; + +import com.google.gson.annotations.SerializedName; + +public class Item { + @SerializedName("id") + private int id; + @SerializedName("feed_id") + private int feedId; + @SerializedName("title") + private String title; + @SerializedName("author") + private String author; + @SerializedName("html") + private String html; + + @SerializedName("url") + private String url; + + @SerializedName("is_saved") + private int isSaved; + @SerializedName("is_read") + private int isRead; + + @SerializedName("created_on_time") + private long createdOnTime; + + public int getId() { + return id; + } + + public int getFeedId() { + return feedId; + } + + public String getTitle() { + return title; + } + + public String getAuthor() { + return author; + } + + public String getHtml() { + return html; + } + + public String getUrl() { + return url; + } + + public int getIsSaved() { + return isSaved; + } + + public int getIsRead() { + return isRead; + } + + public long getCreatedOnTime() { + return createdOnTime; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/Items.java b/app/src/main/java/me/wizos/loread/bean/fever/Items.java new file mode 100644 index 0000000..648c478 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/Items.java @@ -0,0 +1,21 @@ +package me.wizos.loread.bean.fever; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class Items extends BaseResponse { + @SerializedName("total_items") + private String totalItems; + + @SerializedName("items") + private List items; + + public String getTotalItems() { + return totalItems; + } + + public List getItems() { + return items; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/SavedItemIds.java b/app/src/main/java/me/wizos/loread/bean/fever/SavedItemIds.java new file mode 100644 index 0000000..27022bf --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/SavedItemIds.java @@ -0,0 +1,18 @@ +package me.wizos.loread.bean.fever; + +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; + +public class SavedItemIds extends BaseResponse { + @SerializedName("saved_item_ids") + private String savedItemIds; + + public String[] getSavedItemIds(){ + if(TextUtils.isEmpty(savedItemIds)){ + return null; + }else { + return savedItemIds.split(","); + } + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/fever/UnreadItemIds.java b/app/src/main/java/me/wizos/loread/bean/fever/UnreadItemIds.java new file mode 100644 index 0000000..83a3114 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/fever/UnreadItemIds.java @@ -0,0 +1,17 @@ +package me.wizos.loread.bean.fever; + +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; + +public class UnreadItemIds extends BaseResponse { + @SerializedName("unread_item_ids") + private String unreadItemIds; + public String[] getUreadItemIds(){ + if(TextUtils.isEmpty(unreadItemIds)){ + return null; + }else { + return unreadItemIds.split(","); + } + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/GsSubscriptions.java b/app/src/main/java/me/wizos/loread/bean/gson/GsSubscriptions.java deleted file mode 100644 index 090ac72..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/GsSubscriptions.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.wizos.loread.bean.gson; - -import com.google.gson.annotations.SerializedName; - -import java.util.ArrayList; - -/** - * Created by Wizos on 2016/3/11. - */ -public class GsSubscriptions { - @SerializedName("subscriptions") - ArrayList subscriptions; - - public ArrayList getSubscriptions() { - return subscriptions; - } - - public void setSubscriptions(ArrayList subscriptions) { - this.subscriptions = subscriptions; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/GsTags.java b/app/src/main/java/me/wizos/loread/bean/gson/GsTags.java deleted file mode 100644 index 3dd5ca6..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/GsTags.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.wizos.loread.bean.gson; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -import java.util.ArrayList; - -import me.wizos.loread.db.Tag; - -@Parcel -public class GsTags { - @SerializedName("tags") - ArrayList tags; - - public ArrayList getTags() { - return tags; - } - - public void setTags(ArrayList tags) { - this.tags = tags; - } - - -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/StreamContents.java b/app/src/main/java/me/wizos/loread/bean/gson/StreamContents.java deleted file mode 100644 index f201fd4..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/StreamContents.java +++ /dev/null @@ -1,125 +0,0 @@ -package me.wizos.loread.bean.gson; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -import java.util.ArrayList; - -import me.wizos.loread.bean.gson.itemContents.Items; -import me.wizos.loread.bean.gson.itemContents.Self; - - -@Parcel -public class StreamContents { - @SerializedName("direction") - String direction; - - @SerializedName("id") - String id; - - @SerializedName("title") - String title; - - @SerializedName("description") - String description; - - @SerializedName("self") - Self self; - - @SerializedName("updated") - long updated; - - @SerializedName("updatedUsec") - long updatedUsec; - - @SerializedName("items") - ArrayList items; - - @SerializedName("continuation") - String continuation; - -// public StreamContents(){ -// direction = null; -// id = null; -// title = null; -// description = null; -// self = null; -// updated = 0; -// updatedUsec = 0; -// items= new ArrayList<>(); -// continuation = null; -// } - - public String getDirection() { - return direction; - } - - public void setDirection(String direction) { - this.direction = direction; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Self getSelf() { - return self; - } - - public void setSelf(Self self) { - this.self = self; - } - - public long getUpdated() { - return updated; - } - - public void setUpdated(long updated) { - this.updated = updated; - } - - public long getUpdatedUsec() { - return updatedUsec; - } - - public void setUpdatedUsec(long updatedUsec) { - this.updatedUsec = updatedUsec; - } - - public ArrayList getItems() { - return items; - } - - public void setItems(ArrayList items) { - this.items = items; - } - - public String getContinuation() { - return continuation; - } - - public void setContinuation(String continuation) { - this.continuation = continuation; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/StreamPrefs.java b/app/src/main/java/me/wizos/loread/bean/gson/StreamPrefs.java deleted file mode 100644 index e7b3f0e..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/StreamPrefs.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.wizos.loread.bean.gson; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -import java.util.ArrayList; -import java.util.Map; - -@Parcel -public class StreamPrefs { - @SerializedName("streamprefs") - Map> streamPrefs; - - public Map> getStreamPrefsMaps() { - return streamPrefs; - } - - public void setStreamPrefs(Map> streamPrefs) { - this.streamPrefs = streamPrefs; - System.out.println("【StreamPrefs类】"+ streamPrefs ); - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/UserInfo.java b/app/src/main/java/me/wizos/loread/bean/gson/UserInfo.java deleted file mode 100644 index 5d2cb23..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/UserInfo.java +++ /dev/null @@ -1,85 +0,0 @@ -package me.wizos.loread.bean.gson; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -@Parcel -public class UserInfo { - @SerializedName("userId") - long userId; - - @SerializedName("userName") - String userName; - - @SerializedName("userProfileId") - String userProfileId; - - @SerializedName("userEmail") - String userEmail; - - @SerializedName("isBloggerUser") - Boolean isBloggerUser; - - @SerializedName("signupTimeSec") - long signupTimeSec; - - @SerializedName("isMultiLoginEnabled") - Boolean isMultiLoginEnabled; - - public long getUserId() { - return userId; - } - - public void setUserId(long userId) { - this.userId = userId; - } - - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public String getUserProfileId() { - return userProfileId; - } - - public void setUserProfileId(String userProfileId) { - this.userProfileId = userProfileId; - } - - public String getUserEmail() { - return userEmail; - } - - public void setUserEmail(String userEmail) { - this.userEmail = userEmail; - } - - public Boolean getIsBloggerUser() { - return isBloggerUser; - } - - public void setIsBloggerUser(Boolean isBloggerUser) { - this.isBloggerUser = isBloggerUser; - } - - public long getSignupTimeSec() { - return signupTimeSec; - } - - public void setSignupTimeSec(long signupTimeSec) { - this.signupTimeSec = signupTimeSec; - } - - public Boolean getIsMultiLoginEnabled() { - return isMultiLoginEnabled; - } - - public void setIsMultiLoginEnabled(Boolean isMultiLoginEnabled) { - this.isMultiLoginEnabled = isMultiLoginEnabled; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Alternate.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Alternate.java deleted file mode 100644 index 23dc350..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Alternate.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -@Parcel -public class Alternate { - @SerializedName("href") - String href; - - @SerializedName("type") - String type; - - public String getHref() { - return href; - } - - public void setHref(String href) { - this.href = href; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String toString(){ -// System.out.println("【Canonical.toString()】" + "[\"href\": \""+ href + "\"]"); - return "[\"href\": \""+ href + "\",\"type\":\"" + type + "\"]"; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Canonical.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Canonical.java deleted file mode 100644 index e32b4cb..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Canonical.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -@Parcel -public class Canonical { - @SerializedName("href") - String href; - - public String getHref() { - return href; - } - public void setHref(String href) { - this.href = href; - } - - public String toString(){ -// System.out.println("【Canonical.toString()】" + "[\"href\": \""+ href + "\"]"); - return "[\"href\": \""+ href + "\"]"; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Categories.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Categories.java deleted file mode 100644 index c7e47db..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Categories.java +++ /dev/null @@ -1,8 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -/** - * Created by Wizos on 2016/3/13. - */ -public class Categories { - -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Enclosure.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Enclosure.java deleted file mode 100644 index d6bde8d..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Enclosure.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -/** - * 文章中的附件 - * Created by Wizos on 2017/9/3. - */ - -@Parcel -public class Enclosure { - - @SerializedName("href") - String href; - - @SerializedName("type") // 值有image/jpeg、application/rss+xml; charset=UTF-8(href是https://justyy.com/feed) - String type; - - @SerializedName("length") - int length; - - public String getHref() { - return href; - } - - public void setHref(String href) { - this.href = href; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public int getLength() { - return length; - } - - public void setLength(int length) { - this.length = length; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Origin.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Origin.java deleted file mode 100644 index e9dccd9..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Origin.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -@Parcel -public class Origin { - @SerializedName("streamId") - String streamId; - - @SerializedName("title") - String title; - - @SerializedName("htmlUrl") - String htmlUrl; - - - public String getHtmlUrl() { - return htmlUrl; - } - - public void setHtmlUrl(String htmlUrl) { - this.htmlUrl = htmlUrl; - } - - public String getStreamId() { - return streamId; - } - - public void setStreamId(String streamId) { - this.streamId = streamId; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String toString(){ -// System.out.println("【Origin.toString()】" + toString()); - return "{\"streamId\": \""+ streamId + "\",\"title\": \"" + title + "\",\"htmlUrl\": \"" + htmlUrl +"\"}"; - } - -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Self.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Self.java deleted file mode 100644 index 876d700..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Self.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -@Parcel -public class Self { - @SerializedName("href") - String href; - - public String getHref() { - return href; - } - public void setHref(String href) { - this.href = href; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Summary.java b/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Summary.java deleted file mode 100644 index 1f02a89..0000000 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Summary.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; - -import org.parceler.Parcel; - -@Parcel -public class Summary { - @SerializedName("direction") - String direction; - - @SerializedName("content") - String content; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getDirection() { - return direction; - } - - public void setDirection(String direction) { - this.direction = direction; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/EditTag.java b/app/src/main/java/me/wizos/loread/bean/inoreader/EditTag.java new file mode 100644 index 0000000..2122b05 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/EditTag.java @@ -0,0 +1,11 @@ +//package me.wizos.loreadx.bean.inoreader; +// +///** +// * Created by Wizos on 2019/5/12. +// */ +// +//public class EditTag { +// +// private String ac; +// +//} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/GsItemContents.java b/app/src/main/java/me/wizos/loread/bean/inoreader/GsItemContents.java similarity index 100% rename from app/src/main/java/me/wizos/loread/bean/gson/GsItemContents.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/GsItemContents.java diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/GsTag.java b/app/src/main/java/me/wizos/loread/bean/inoreader/GsTag.java new file mode 100644 index 0000000..bafd0be --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/GsTag.java @@ -0,0 +1,30 @@ +package me.wizos.loread.bean.inoreader; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by Wizos on 2019/2/20. + */ + +public class GsTag { + @SerializedName("id") + String id; + @SerializedName("sortid") + String sortid; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSortid() { + return sortid; + } + + public void setSortid(String sortid) { + this.sortid = sortid; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/GsTags.java b/app/src/main/java/me/wizos/loread/bean/inoreader/GsTags.java new file mode 100644 index 0000000..2bd6780 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/GsTags.java @@ -0,0 +1,23 @@ +package me.wizos.loread.bean.inoreader; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +import java.util.ArrayList; + +import me.wizos.loread.db.Category; + +@Parcel +public class GsTags { + @SerializedName("tags") + ArrayList categories; + + public ArrayList getCategories() { + return categories; + } + + public void setCategories(ArrayList categories) { + this.categories = categories; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/GsUnreadCount.java b/app/src/main/java/me/wizos/loread/bean/inoreader/GsUnreadCount.java similarity index 93% rename from app/src/main/java/me/wizos/loread/bean/gson/GsUnreadCount.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/GsUnreadCount.java index f97e5f4..9d91089 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/GsUnreadCount.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/GsUnreadCount.java @@ -1,4 +1,4 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; @@ -17,6 +17,7 @@ public class GsUnreadCount { public int getMax() { return max; } + public void setMax(int max) { this.max = max; } @@ -24,6 +25,7 @@ public void setMax(int max) { public ArrayList getUnreadcounts() { return unreadcounts; } + public void setUnreadcounts(ArrayList unreadcounts) { this.unreadcounts = unreadcounts; } diff --git a/app/src/main/java/me/wizos/loread/bean/gson/ItemIDs.java b/app/src/main/java/me/wizos/loread/bean/inoreader/ItemIds.java similarity index 89% rename from app/src/main/java/me/wizos/loread/bean/gson/ItemIDs.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/ItemIds.java index 8d208c7..2668b96 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/ItemIDs.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/ItemIds.java @@ -1,16 +1,13 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; import java.util.ArrayList; -import me.wizos.loread.bean.gson.son.ItemRefs; - /** * Created by Wizos on 2016/3/10. */ -public class ItemIDs { - +public class ItemIds { @SerializedName("items") ArrayList items; @@ -20,7 +17,7 @@ public class ItemIDs { @SerializedName("continuation") String continuation; - public ItemIDs() { + public ItemIds() { items = new ArrayList<>(); itemRefs = new ArrayList<>(); continuation = null; @@ -38,6 +35,7 @@ public void setItems(ArrayList items) { public void setItemRefs(ArrayList itemRefs) { this.itemRefs = itemRefs; } + public ArrayList getItemRefs() { return itemRefs; } @@ -45,6 +43,7 @@ public ArrayList getItemRefs() { public void setContinuation(String continuation) { this.continuation = continuation; } + public String getContinuation() { return continuation; } diff --git a/app/src/main/java/me/wizos/loread/bean/gson/son/ItemRefs.java b/app/src/main/java/me/wizos/loread/bean/inoreader/ItemRefs.java similarity index 97% rename from app/src/main/java/me/wizos/loread/bean/gson/son/ItemRefs.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/ItemRefs.java index c66d49c..5a34492 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/son/ItemRefs.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/ItemRefs.java @@ -1,4 +1,4 @@ -package me.wizos.loread.bean.gson.son; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; @@ -29,6 +29,7 @@ public void setId(String id) { public void setDirectStreamIds(ArrayList directStreamIds) { this.directStreamIds = directStreamIds; } + public ArrayList getDirectStreamIds() { return directStreamIds; } @@ -36,6 +37,7 @@ public ArrayList getDirectStreamIds() { public void setTimestampUsec(long timestampUsec) { this.timestampUsec = timestampUsec; } + public long getTimestampUsec() { return timestampUsec; } diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/LoginResult.java b/app/src/main/java/me/wizos/loread/bean/inoreader/LoginResult.java new file mode 100644 index 0000000..98c84de --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/LoginResult.java @@ -0,0 +1,68 @@ +package me.wizos.loread.bean.inoreader; + +import android.text.TextUtils; + +import me.wizos.loread.App; +import me.wizos.loread.R; + +/** + * Created by Wizos on 2019/3/27. + */ + +public class LoginResult { + public boolean success = false; + + private String error; + private String auth; + + public LoginResult(String result) { + if (TextUtils.isEmpty(result)) { + return; + } + + error = App.i().getString(R.string.wrong_unknown); + + String[] info = result.split("\n"); + if (info.length == 0) { + return; + } + + if (info[0].startsWith("Error=")) { + error = info[0].replace("Error=", ""); + if (error.toLowerCase().equals("wrong_username_or_password")) { + error = App.i().getString(R.string.wrong_username_or_password); + } + } else { + for (String tmp : info) { + if (tmp.startsWith("Auth=")) { + success = true; + auth = "GoogleLogin " + tmp; + } + } + } + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getAuth() { + return auth; + } + + public void setAuth(String auth) { + this.auth = auth; + } + + @Override + public String toString() { + return "LoginResult{" + + "error='" + error + '\'' + + ", auth='" + auth + '\'' + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/Readability.java b/app/src/main/java/me/wizos/loread/bean/inoreader/Readability.java similarity index 98% rename from app/src/main/java/me/wizos/loread/bean/gson/Readability.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/Readability.java index 7a4399f..bdbeb87 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/Readability.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/Readability.java @@ -1,4 +1,4 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/StreamContents.java b/app/src/main/java/me/wizos/loread/bean/inoreader/StreamContents.java new file mode 100644 index 0000000..e80e37c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/StreamContents.java @@ -0,0 +1,111 @@ +package me.wizos.loread.bean.inoreader; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +import java.util.ArrayList; + +import me.wizos.loread.bean.inoreader.itemContents.Item; +import me.wizos.loread.bean.inoreader.itemContents.Self; + + +@Parcel +public class StreamContents { + @SerializedName("id") + String id; + @SerializedName("title") + String title; + + @SerializedName("items") + ArrayList items; + + @SerializedName("continuation") + String continuation; + + @SerializedName("updated") + long updated; + // ino专用 + String description; + // ino专用 + @SerializedName("updatedUsec") + long updatedUsec; + // ino专用 + @SerializedName("self") + Self self; + // ino专用 + @SerializedName("direction") + String direction; + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Self getSelf() { + return self; + } + + public void setSelf(Self self) { + this.self = self; + } + + public long getUpdated() { + return updated; + } + + public void setUpdated(long updated) { + this.updated = updated; + } + + public long getUpdatedUsec() { + return updatedUsec; + } + + public void setUpdatedUsec(long updatedUsec) { + this.updatedUsec = updatedUsec; + } + + public ArrayList getItems() { + return items; + } + + public void setItems(ArrayList items) { + this.items = items; + } + + public String getContinuation() { + return continuation; + } + + public void setContinuation(String continuation) { + this.continuation = continuation; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/StreamPref.java b/app/src/main/java/me/wizos/loread/bean/inoreader/StreamPref.java similarity index 88% rename from app/src/main/java/me/wizos/loread/bean/gson/StreamPref.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/StreamPref.java index d1dc0cf..3c86a4d 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/StreamPref.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/StreamPref.java @@ -1,8 +1,9 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; -/** ID 和 Value +/** + * ID 和 Value * Created by Wizos on 2016/3/5. */ public class StreamPref { diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/StreamPrefs.java b/app/src/main/java/me/wizos/loread/bean/inoreader/StreamPrefs.java new file mode 100644 index 0000000..7566048 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/StreamPrefs.java @@ -0,0 +1,23 @@ +package me.wizos.loread.bean.inoreader; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +import java.util.ArrayList; +import java.util.Map; + +@Parcel +public class StreamPrefs { + @SerializedName("streamprefs") + Map> streamPrefs; + + public Map> getStreamPrefsMaps() { + return streamPrefs; + } + + public void setStreamPrefs(Map> streamPrefs) { + this.streamPrefs = streamPrefs; + System.out.println("【StreamPrefs类】" + streamPrefs); + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/SubCategories.java b/app/src/main/java/me/wizos/loread/bean/inoreader/SubCategories.java similarity index 92% rename from app/src/main/java/me/wizos/loread/bean/gson/SubCategories.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/SubCategories.java index f54e921..da71b11 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/SubCategories.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/SubCategories.java @@ -1,4 +1,4 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; diff --git a/app/src/main/java/me/wizos/loread/bean/gson/Subscriptions.java b/app/src/main/java/me/wizos/loread/bean/inoreader/Subscription.java similarity index 57% rename from app/src/main/java/me/wizos/loread/bean/gson/Subscriptions.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/Subscription.java index 33267a2..aa331a6 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/Subscriptions.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/Subscription.java @@ -1,93 +1,92 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; import java.util.ArrayList; +import me.wizos.loread.App; +import me.wizos.loread.db.Feed; + /** * Created by Wizos on 2016/3/11. */ -public class Subscriptions { +public class Subscription { @SerializedName("id") - String id; - + private String id; @SerializedName("title") - String title; - + private String title; @SerializedName("categories") - ArrayList categories; - + private ArrayList categories; @SerializedName("sortid") - String sortid; - + private String sortId; @SerializedName("firstitemmsec") - long firstitemmsec; - + private long firstItemMsec; @SerializedName("url") - String url; - + private String url; @SerializedName("htmlUrl") - String htmlUrl; - + private String htmlUrl; @SerializedName("iconUrl") - String iconUrl; + private String iconUrl; + public Feed convert2Feed() { + Feed feed = new Feed(); + feed.setId(id); + feed.setTitle(title); + feed.setFeedUrl(url); + feed.setHtmlUrl(htmlUrl); + feed.setIconUrl(iconUrl); + feed.setDisplayMode(App.OPEN_MODE_RSS); + return feed; + } + public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } - public ArrayList getCategories() { return categories; } public void setCategories(ArrayList categories) { this.categories = categories; } - - public String getSortid() { - return sortid; + public String getSortId() { + return sortId; } - public void setSortid(String sortid) { - this.sortid = sortid; + public void setSortId(String sortId) { + this.sortId = sortId; } - - public long getFirstitemmsec() { - return firstitemmsec; + public long getFirstItemMsec() { + return firstItemMsec; } - - public void setFirstitemmsec(long firstitemmsec) { - this.firstitemmsec = firstitemmsec; + public void setFirstItemMsec(long firstItemMsec) { + this.firstItemMsec = firstItemMsec; } - public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } - public String getHtmlUrl() { return htmlUrl; } public void setHtmlUrl(String htmlUrl) { this.htmlUrl = htmlUrl; } - public String getIconUrl() { return iconUrl; } - public void setIconUrl(String iconUrl) { this.iconUrl = iconUrl; } + } diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/Subscriptions.java b/app/src/main/java/me/wizos/loread/bean/inoreader/Subscriptions.java new file mode 100644 index 0000000..83cc0f1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/Subscriptions.java @@ -0,0 +1,16 @@ +package me.wizos.loread.bean.inoreader; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +/** + * Created by Wizos on 2016/3/11. + */ +public class Subscriptions { + @SerializedName("subscriptions") + private ArrayList subscriptions; + public ArrayList getSubscriptions() { + return subscriptions; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/UnreadCounts.java b/app/src/main/java/me/wizos/loread/bean/inoreader/UnreadCounts.java similarity index 94% rename from app/src/main/java/me/wizos/loread/bean/gson/UnreadCounts.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/UnreadCounts.java index 4b8c5ed..a2891e6 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/UnreadCounts.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/UnreadCounts.java @@ -1,4 +1,4 @@ -package me.wizos.loread.bean.gson; +package me.wizos.loread.bean.inoreader; import com.google.gson.annotations.SerializedName; @@ -19,6 +19,7 @@ public class UnreadCounts { public String getId() { return id; } + public void setId(String id) { this.id = id; } @@ -26,6 +27,7 @@ public void setId(String id) { public int getCount() { return count; } + public void setCount(int count) { this.count = count; } @@ -33,6 +35,7 @@ public void setCount(int count) { public long getNewestItemTimestampUsec() { return newestItemTimestampUsec; } + public void setNewestItemTimestampUsec(long newestItemTimestampUsec) { this.newestItemTimestampUsec = newestItemTimestampUsec; } diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/UserInfo.java b/app/src/main/java/me/wizos/loread/bean/inoreader/UserInfo.java new file mode 100644 index 0000000..d76ffbf --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/UserInfo.java @@ -0,0 +1,97 @@ +package me.wizos.loread.bean.inoreader; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +import me.wizos.loread.Contract; +import me.wizos.loread.db.User; + +@Parcel +public class UserInfo { + @SerializedName("userId") + long userId; + @SerializedName("userProfileId") + String userProfileId; + + @SerializedName("userName") + String userName; + + @SerializedName("userEmail") + String userEmail; + + @SerializedName("isBloggerUser") + Boolean isBloggerUser; + + @SerializedName("signupTimeSec") + long signupTimeSec; + + @SerializedName("isMultiLoginEnabled") + Boolean isMultiLoginEnabled; + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getUserProfileId() { + return userProfileId; + } + + public void setUserProfileId(String userProfileId) { + this.userProfileId = userProfileId; + } + + public String getUserEmail() { + return userEmail; + } + + public void setUserEmail(String userEmail) { + this.userEmail = userEmail; + } + + public Boolean getIsBloggerUser() { + return isBloggerUser; + } + + public void setIsBloggerUser(Boolean isBloggerUser) { + this.isBloggerUser = isBloggerUser; + } + + public long getSignupTimeSec() { + return signupTimeSec; + } + + public void setSignupTimeSec(long signupTimeSec) { + this.signupTimeSec = signupTimeSec; + } + + public Boolean getIsMultiLoginEnabled() { + return isMultiLoginEnabled; + } + + public void setIsMultiLoginEnabled(Boolean isMultiLoginEnabled) { + this.isMultiLoginEnabled = isMultiLoginEnabled; + } + + public User getUser() { + User user = new User(); + user.setSource(Contract.PROVIDER_INOREADER); + user.setId(Contract.PROVIDER_INOREADER + "_" + userId); + user.setUserId(userId + ""); + user.setUserEmail(userEmail); + user.setUserName(userName); + return user; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Items.java b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Item.java similarity index 56% rename from app/src/main/java/me/wizos/loread/bean/gson/itemContents/Items.java rename to app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Item.java index ed89a8f..317804c 100644 --- a/app/src/main/java/me/wizos/loread/bean/gson/itemContents/Items.java +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Item.java @@ -1,55 +1,32 @@ -package me.wizos.loread.bean.gson.itemContents; - -import com.google.gson.annotations.SerializedName; +package me.wizos.loread.bean.inoreader.itemContents; import java.util.ArrayList; +import me.wizos.loread.App; +import me.wizos.loread.bean.Enclosure; +import me.wizos.loread.db.Article; +import me.wizos.loread.network.api.BaseApi; +import me.wizos.loread.utils.ArticleUtil; + /** * Stream content 和 Item content (貌似已被官方弃用)两个api返回指内的文章项 * Created by Wizos on 2016/3/11. */ -public class Items { - @SerializedName("crawlTimeMsec") - private long crawlTimeMsec; - - @SerializedName("timestampUsec") - private long timestampUsec; - - @SerializedName("id") +public class Item { private String id; - - @SerializedName("categories") - private ArrayList categories; - - @SerializedName("title") private String title; - - @SerializedName("published") private long published; - - @SerializedName("updated") private long updated; + private long crawlTimeMsec; + private long timestampUsec; + private ArrayList categories; - @SerializedName("starred") // 加星的时间 - private long starred; - - // 附件:这个还不知道是什么用处,不过可以显示图片 - @SerializedName("enclosure") - private ArrayList enclosure; - - @SerializedName("canonical") - private ArrayList canonical; - - @SerializedName("alternate") - private ArrayList alternate; - - @SerializedName("summary") + private long starred; // 加星的时间 + private ArrayList enclosure; // 附件:这个还不知道是什么用处,不过可以显示图片 + private ArrayList canonical; + private ArrayList alternate; private Summary summary; - - @SerializedName("author") private String author; - - @SerializedName("origin") private Origin origin; //这应该是开启了社交后才会有的字段 @@ -131,19 +108,19 @@ public void setEnclosure(ArrayList enclosure) { this.enclosure = enclosure; } - public ArrayList getCanonical() { + public ArrayList getCanonical() { return canonical; } - public void setCanonical(ArrayList canonical) { + public void setCanonical(ArrayList canonical) { this.canonical = canonical; } - public ArrayList getAlternate() { + public ArrayList getAlternate() { return alternate; } - public void setAlternate(ArrayList alternate) { + public void setAlternate(ArrayList alternate) { this.alternate = alternate; } @@ -172,8 +149,42 @@ public void setOrigin(Origin origin) { } -// // // TEST: 学习 FeedMe 中的写法。直接把解析写入实体类中 -// public static Items parse(String json){ -// return new Gson().fromJson(json, Items.class); -// } + public Article convert(BaseApi.ArticleChanger articleChanger) { + String tempTitle; + Article article = new Article(); + // 返回的字段 + article.setId(id); + + title = ArticleUtil.getOptimizedTitle(title); + article.setTitle(title); + + article.setAuthor(author); + article.setPubDate(published * 1000); + + if (canonical != null && canonical.size() > 0) { + article.setLink(canonical.get(0).getHref()); + } + if (origin != null) { + article.setFeedId(origin.getStreamId()); + article.setFeedTitle(origin.getTitle()); + } + + String tmpContent = ArticleUtil.getOptimizedContent(article.getLink(), summary.getContent()); + tmpContent = ArticleUtil.getOptimizedContentWithEnclosures(tmpContent,enclosure); + article.setContent(tmpContent); + + String tmpSummary = ArticleUtil.getOptimizedSummary(tmpContent); + article.setSummary(tmpSummary); + + String coverUrl = ArticleUtil.getCoverUrl(article.getLink(),tmpContent); + article.setImage(coverUrl); + + // 自己设置的字段 + // KLog.i("【增加文章】" + article.getId()); + article.setSaveStatus(App.STATUS_NOT_FILED); + if (articleChanger != null) { + articleChanger.change(article); + } + return article; + } } diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Origin.java b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Origin.java new file mode 100644 index 0000000..a65f3eb --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Origin.java @@ -0,0 +1,47 @@ +package me.wizos.loread.bean.inoreader.itemContents; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +@Parcel +public class Origin { + @SerializedName("streamId") + String streamId; + + @SerializedName("title") + String title; + + @SerializedName("htmlUrl") + String htmlUrl; + + + public String getHtmlUrl() { + return htmlUrl; + } + + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; + } + + public String getStreamId() { + return streamId; + } + + public void setStreamId(String streamId) { + this.streamId = streamId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String toString() { + return "{\"streamId\": \"" + streamId + "\",\"title\": \"" + title + "\",\"htmlUrl\": \"" + htmlUrl + "\"}"; + } + +} diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Self.java b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Self.java new file mode 100644 index 0000000..02ba50d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Self.java @@ -0,0 +1,19 @@ +package me.wizos.loread.bean.inoreader.itemContents; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +@Parcel +public class Self { + @SerializedName("href") + String href; + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Summary.java b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Summary.java new file mode 100644 index 0000000..bb6624a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/inoreader/itemContents/Summary.java @@ -0,0 +1,30 @@ +package me.wizos.loread.bean.inoreader.itemContents; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +@Parcel +public class Summary { + @SerializedName("direction") + String direction; + + @SerializedName("content") + String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/loread/LoginParam.java b/app/src/main/java/me/wizos/loread/bean/loread/LoginParam.java new file mode 100644 index 0000000..f1a44e7 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/loread/LoginParam.java @@ -0,0 +1,32 @@ +//package me.wizos.loread.bean.loread; +// +//public class LoginParam extends RequestJsonBody{ +// private String user = "admin"; +// private String password; +// private String op = "login"; +// +// public String getUser() { +// return user; +// } +// +// public void setUser(String user) { +// this.user = user; +// } +// +// public String getPassword() { +// return password; +// } +// +// public void setPassword(String password) { +// this.password = password; +// } +// +// @Override +// public String toString() { +// return "login{" + +// "user='" + user + '\'' + +// ", password='" + password + '\'' + +// ", op='" + op + '\'' + +// '}'; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/loread/RequestJsonBody.java b/app/src/main/java/me/wizos/loread/bean/loread/RequestJsonBody.java new file mode 100644 index 0000000..14c93b8 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/loread/RequestJsonBody.java @@ -0,0 +1,21 @@ +//package me.wizos.loread.bean.loread; +// +//public class RequestJsonBody { +// private String op; +// private String sid; +// +// public void setSid(String sid) { +// this.sid = sid; +// } +// public String getSid() { +// return sid; +// } +// +// public String getOp() { +// return op; +// } +// +// public void setOp(String op) { +// this.op = op; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/loread/Response.java b/app/src/main/java/me/wizos/loread/bean/loread/Response.java new file mode 100644 index 0000000..95eed02 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/loread/Response.java @@ -0,0 +1,44 @@ +//package me.wizos.loread.bean.loread; +// +//import com.google.gson.annotations.SerializedName; +//import com.socks.library.KLog; +// +//public class Response { +// private int code; +// @SerializedName(value = "msg", alternate = {"error"}) +// private String msg; +// private T data; +// +// public boolean isSuccessful() { +// if (code == 0) { +// KLog.i("请求正常"); +// return true; +// } +// KLog.i("请求异常:" + data); +// return false; +// } +// +// public int getCode() { +// return code; +// } +// +// public void setCode(int code) { +// this.code = code; +// } +// +// public T getData() { +// return data; +// } +// +// public void setData(T data) { +// this.data = data; +// } +// +// public String getMsg() { +// return msg; +// } +// +// public void setMsg(String msg) { +// this.msg = msg; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/proxynode/AnonymityLevel.java b/app/src/main/java/me/wizos/loread/bean/proxynode/AnonymityLevel.java new file mode 100644 index 0000000..a3d4e40 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/proxynode/AnonymityLevel.java @@ -0,0 +1,49 @@ +package me.wizos.loread.bean.proxynode; + +import me.wizos.loread.gson.GsonEnum; + +public enum AnonymityLevel implements GsonEnum { +// TRANSPARENT, //"透明代理" +// ANONYMOUS, // "匿名代理" +// DISTORTING, // "欺骗性代理" +// ELITE, // "高匿代理" +// UNKNOW //"未知代理" + + TRANSPARENT("TRANSPARENT"), ANONYMOUS("ANONYMOUS"), DISTORTING("DISTORTING"), ELITE("ELITE"), UNKNOW("UNKNOW"); + private final String anonymityLevel; + AnonymityLevel(String anonymityLevel) { + this.anonymityLevel = anonymityLevel; + } + + + public String getAnonymityLevel() { + return anonymityLevel; + } + + public static AnonymityLevel parse(String level) { + switch (level) { + case "TRANSPARENT": + return AnonymityLevel.TRANSPARENT; + case "ANONYMOUS": + return AnonymityLevel.ANONYMOUS; + case "DISTORTING": + return AnonymityLevel.DISTORTING; + case "ELITE": + return AnonymityLevel.ELITE; + case "UNKNOW": + return AnonymityLevel.UNKNOW; + default: + throw new IllegalArgumentException("There is not enum names with [" + level + "] of type AnonymityLevel exists! "); + } + } + + @Override + public AnonymityLevel deserialize(String jsonEnum) { + return AnonymityLevel.parse(jsonEnum); + } + + @Override + public String serialize() { + return this.getAnonymityLevel(); + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/proxynode/ProxyNode.java b/app/src/main/java/me/wizos/loread/bean/proxynode/ProxyNode.java new file mode 100644 index 0000000..1e0152c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/proxynode/ProxyNode.java @@ -0,0 +1,49 @@ +package me.wizos.loread.bean.proxynode; + +import java.io.Serializable; +import java.net.Proxy; + +/** + * 代理节点并不能被用于翻墙,因为会被“GFW”给发现,并重置连接。 + * 只能用于国内反爬虫 + */ +public class ProxyNode implements Serializable { + public ProxyType type; + public AnonymityLevel level; + public String hostname; + public int port; + + public ProxyNode() { + } + + public ProxyNode(ProxyType type, AnonymityLevel level, String hostname, int port) { + this.type = type; + this.level = level; + this.hostname = hostname; + this.port = port; + } + + @Override + public String toString() { + return "ProxyConfig{" + + "type=" + type + + ", level=" + level + + ", hostname='" + hostname + '\'' + + ", port=" + port + + '}'; + } + + + public Proxy.Type getProxyType() { + switch (type) { + case DIRECT: + return Proxy.Type.DIRECT; + case HTTP: + return Proxy.Type.HTTP; + case SOCKS: + return Proxy.Type.SOCKS; + default: + throw new IllegalArgumentException("There is not enum names with [" + level + "] of type AnonymityLevel exists! "); + } + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/proxynode/ProxyType.java b/app/src/main/java/me/wizos/loread/bean/proxynode/ProxyType.java new file mode 100644 index 0000000..8124bbc --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/proxynode/ProxyType.java @@ -0,0 +1,39 @@ +package me.wizos.loread.bean.proxynode; + +import me.wizos.loread.gson.GsonEnum; + +public enum ProxyType implements GsonEnum { + DIRECT("DIRECT"), HTTP("HTTP"), SOCKS("SOCKS"); + private final String proxyType; + ProxyType(String proxyType) { + this.proxyType = proxyType; + } + + + public String getProxyType() { + return proxyType; + } + + public static ProxyType parse(String type) { + switch (type) { + case "DIRECT": + return ProxyType.DIRECT; + case "HTTP": + return ProxyType.HTTP; + case "SOCKS": + return ProxyType.SOCKS; + default: + throw new IllegalArgumentException("There is not enum names with [" + type + "] of type AnonymityLevel exists! "); + } + } + + @Override + public ProxyType deserialize(String jsonEnum) { + return ProxyType.parse(jsonEnum); + } + + @Override + public String serialize() { + return this.getProxyType(); + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/search/FeedlyFeed.java b/app/src/main/java/me/wizos/loread/bean/search/FeedlyFeed.java deleted file mode 100644 index c192393..0000000 --- a/app/src/main/java/me/wizos/loread/bean/search/FeedlyFeed.java +++ /dev/null @@ -1,199 +0,0 @@ -package me.wizos.loread.bean.search; - -import com.google.gson.annotations.SerializedName; - -import java.util.ArrayList; - -/** - * Created by Wizos on 2017/12/31. - */ - -public class FeedlyFeed { - - @SerializedName("title") - String title; - - @SerializedName("feedId") - String feedId; - - @SerializedName("website") - String website; - -// @SerializedName("iconUrl") -// String iconUrl; - - @SerializedName("description") - String description; - - @SerializedName("subscribers") - int subscribers; - - @SerializedName("lastUpdated") - long lastUpdated; - - @SerializedName("score") - String score; - - @SerializedName("velocity") // 每周发布的文章的平均数量。 此号码每隔几天更新一次。 - String velocity; - - @SerializedName("coverage") - String coverage; - - @SerializedName("coverageScore") - String coverageScore; - - @SerializedName("visualUrl") // 此供稿的图标网址。 - String visualUrl; - - @SerializedName("deliciousTags") - ArrayList deliciousTags; - - @SerializedName("scheme") - String scheme; - - @SerializedName("contentType") - String contentType; - - @SerializedName("language") - String language; - - @SerializedName("partial") - boolean partial; - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getFeedId() { - return feedId; - } - - public void setFeedId(String feedId) { - this.feedId = feedId; - } - - public String getWebsite() { - return website; - } - - public void setWebsite(String website) { - this.website = website; - } - -// public String getIconUrl() { -// return iconUrl; -// } -// -// public void setIconUrl(String iconUrl) { -// this.iconUrl = iconUrl; -// } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public int getSubscribers() { - return subscribers; - } - - public void setSubscribers(int subscribers) { - this.subscribers = subscribers; - } - - public long getLastUpdated() { - return lastUpdated; - } - - public void setLastUpdated(long lastUpdated) { - this.lastUpdated = lastUpdated; - } - - public String getScore() { - return score; - } - - public void setScore(String score) { - this.score = score; - } - - public String getVelocity() { - return velocity; - } - - public void setVelocity(String velocity) { - this.velocity = velocity; - } - - public String getCoverage() { - return coverage; - } - - public void setCoverage(String coverage) { - this.coverage = coverage; - } - - public String getCoverageScore() { - return coverageScore; - } - - public void setCoverageScore(String coverageScore) { - this.coverageScore = coverageScore; - } - - public String getVisualUrl() { - return visualUrl; - } - - public void setVisualUrl(String visualUrl) { - this.visualUrl = visualUrl; - } - - public ArrayList getDeliciousTags() { - return deliciousTags; - } - - public void setDeliciousTags(ArrayList deliciousTags) { - this.deliciousTags = deliciousTags; - } - - public String getScheme() { - return scheme; - } - - public void setScheme(String scheme) { - this.scheme = scheme; - } - - public String getContentType() { - return contentType; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public boolean isPartial() { - return partial; - } - - public void setPartial(boolean partial) { - this.partial = partial; - } -} diff --git a/app/src/main/java/me/wizos/loread/bean/search/QuickAdd.java b/app/src/main/java/me/wizos/loread/bean/search/QuickAdd.java index 90e1d18..0ad4e2f 100644 --- a/app/src/main/java/me/wizos/loread/bean/search/QuickAdd.java +++ b/app/src/main/java/me/wizos/loread/bean/search/QuickAdd.java @@ -1,50 +1,50 @@ -package me.wizos.loread.bean.search; - -import com.google.gson.annotations.SerializedName; - -/** - * Created by Wizos on 2018/1/2. - */ - -public class QuickAdd { - @SerializedName("streamId") - String streamId; - @SerializedName("streamName") - String streamName; - @SerializedName("query") - String query; - @SerializedName("numResults") // 如果Feed由于某种原因未被添加,则numResults将为0。 即使用户已订阅,添加订阅源时也会为1。 - int numResults; - - public String getStreamId() { - return streamId; - } - - public void setStreamId(String streamId) { - this.streamId = streamId; - } - - public String getStreamName() { - return streamName; - } - - public void setStreamName(String streamName) { - this.streamName = streamName; - } - - public String getQuery() { - return query; - } - - public void setQuery(String query) { - this.query = query; - } - - public int getNumResults() { - return numResults; - } - - public void setNumResults(int numResults) { - this.numResults = numResults; - } -} +//package me.wizos.loreadx.bean.search; +// +//import com.google.gson.annotations.SerializedName; +// +///** +// * Created by Wizos on 2018/1/2. +// */ +// +//public class QuickAdd { +// @SerializedName("streamId") +// String streamId; +// @SerializedName("streamName") +// String streamName; +// @SerializedName("query") +// String query; +// @SerializedName("numResults") // 如果Feed由于某种原因未被添加,则numResults将为0。 即使用户已订阅,添加订阅源时也会为1。 +// int numResults; +// +// public String getStreamId() { +// return streamId; +// } +// +// public void setStreamId(String streamId) { +// this.streamId = streamId; +// } +// +// public String getStreamName() { +// return streamName; +// } +// +// public void setStreamName(String streamName) { +// this.streamName = streamName; +// } +// +// public String getQuery() { +// return query; +// } +// +// public void setQuery(String query) { +// this.query = query; +// } +// +// public int getNumResults() { +// return numResults; +// } +// +// public void setNumResults(int numResults) { +// this.numResults = numResults; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/search/SearchFeedItem.java b/app/src/main/java/me/wizos/loread/bean/search/SearchFeedItem.java new file mode 100644 index 0000000..0581313 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/search/SearchFeedItem.java @@ -0,0 +1,67 @@ +package me.wizos.loread.bean.search; + +import me.wizos.loread.bean.feedly.FeedItem; + +/** + * Created by Wizos on 2017/12/31. + */ + +public class SearchFeedItem extends FeedItem { + private long lastUpdated; // 可能用不到吧,和 Updated 字段类似 + private float score; + private float coverage; + private float coverageScore; + private float averageReadTime; + private String websiteTitle; + // private int totalTagCount; + // private ArrayList<> tagCounts; + // private ArrayList deliciousTags; + + public long getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(long lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public float getScore() { + return score; + } + + public void setScore(float score) { + this.score = score; + } + + public float getCoverage() { + return coverage; + } + + public void setCoverage(float coverage) { + this.coverage = coverage; + } + + public float getCoverageScore() { + return coverageScore; + } + + public void setCoverageScore(float coverageScore) { + this.coverageScore = coverageScore; + } + + public float getAverageReadTime() { + return averageReadTime; + } + + public void setAverageReadTime(float averageReadTime) { + this.averageReadTime = averageReadTime; + } + + public String getWebsiteTitle() { + return websiteTitle; + } + + public void setWebsiteTitle(String websiteTitle) { + this.websiteTitle = websiteTitle; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/search/FeedlyFeedsSearchResult.java b/app/src/main/java/me/wizos/loread/bean/search/SearchFeeds.java similarity index 55% rename from app/src/main/java/me/wizos/loread/bean/search/FeedlyFeedsSearchResult.java rename to app/src/main/java/me/wizos/loread/bean/search/SearchFeeds.java index 0c94db2..4a44009 100644 --- a/app/src/main/java/me/wizos/loread/bean/search/FeedlyFeedsSearchResult.java +++ b/app/src/main/java/me/wizos/loread/bean/search/SearchFeeds.java @@ -1,28 +1,17 @@ package me.wizos.loread.bean.search; -import com.google.gson.annotations.SerializedName; - import java.util.ArrayList; /** * Created by Wizos on 2017/12/31. */ -public class FeedlyFeedsSearchResult { - @SerializedName("hint") - String hint; - - @SerializedName("related") - ArrayList related; - - @SerializedName("results") - ArrayList results; - - @SerializedName("queryType") - String queryType; - - @SerializedName("scheme") - String scheme; +public class SearchFeeds { + private String hint; + private ArrayList related; + private ArrayList results; + private String queryType; + private String scheme; public String getHint() { return hint; @@ -56,11 +45,22 @@ public void setRelated(ArrayList related) { this.related = related; } - public ArrayList getResults() { + public ArrayList getResults() { return results; } - public void setResults(ArrayList results) { + public void setResults(ArrayList results) { this.results = results; } + + @Override + public String toString() { + return "SearchFeeds{" + + "hint='" + hint + '\'' + + ", related=" + related + + ", results=" + results + + ", queryType='" + queryType + '\'' + + ", scheme='" + scheme + '\'' + + '}'; + } } diff --git a/app/src/main/java/me/wizos/loread/bean/search/StreamFeed.java b/app/src/main/java/me/wizos/loread/bean/search/StreamFeed.java index 9379152..1e584cd 100644 --- a/app/src/main/java/me/wizos/loread/bean/search/StreamFeed.java +++ b/app/src/main/java/me/wizos/loread/bean/search/StreamFeed.java @@ -1,29 +1,27 @@ -package me.wizos.loread.bean.search; - -import com.google.gson.annotations.SerializedName; - -/** - * Created by Wizos on 2017/12/31. - */ - -public class StreamFeed { - @SerializedName("title") - String title; - - @SerializedName("feedUrl") - String feedUrl; - - @SerializedName("iconUrl") - String iconUrl; - - @SerializedName("description") - String description; - - @SerializedName("subscribers") - int subscribers; - - @SerializedName("iconUrl") - int articlesPerWeek; - - -} +//package me.wizos.loreadx.bean.search; +// +//import com.google.gson.annotations.SerializedName; +// +///** +// * Created by Wizos on 2017/12/31. +// */ +// +//public class StreamFeed { +// @SerializedName("title") +// String title; +// +// @SerializedName("feedUrl") +// String feedUrl; +// +// @SerializedName("iconUrl") +// String iconUrl; +// +// @SerializedName("description") +// String description; +// +// @SerializedName("subscribers") +// int subscribers; +// +// @SerializedName("iconUrl") +// int articlesPerWeek; +//} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetArticles.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetArticles.java new file mode 100644 index 0000000..a7617f9 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetArticles.java @@ -0,0 +1,35 @@ +package me.wizos.loread.bean.ttrss.request; + +import com.google.gson.annotations.SerializedName; + +import java.util.HashSet; +import java.util.List; + +import me.wizos.loread.utils.StringUtils; + +public class GetArticles { + private String sid; + private String op = "getArticle"; + @SerializedName("article_id") + private String articleIds; + + public GetArticles(String sid) { + this.sid = sid; + } + + public void setArticleIds(String articleIds) { + this.articleIds = articleIds; + } + public void setArticleIds(HashSet articleIdSet) { + this.articleIds = StringUtils.join(",",articleIdSet); + } + public void setArticleIds(List articleIdList) { + this.articleIds = StringUtils.join(",",articleIdList); + } + public void setSid(String sid) { + this.sid = sid; + } + public String getSid() { + return sid; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetCategories.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetCategories.java new file mode 100644 index 0000000..6fb840b --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetCategories.java @@ -0,0 +1,36 @@ +package me.wizos.loread.bean.ttrss.request; + +public class GetCategories { + private String sid; + private String op = "getCategories"; + private boolean unread_only = false; + private boolean include_empty = true; + + public GetCategories(String sid) { + this.sid = sid; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public boolean isUnread_only() { + return unread_only; + } + + public void setUnread_only(boolean unread_only) { + this.unread_only = unread_only; + } + + public boolean isInclude_empty() { + return include_empty; + } + + public void setInclude_empty(boolean include_empty) { + this.include_empty = include_empty; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetFeeds.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetFeeds.java new file mode 100644 index 0000000..bbf75c5 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetFeeds.java @@ -0,0 +1,45 @@ +package me.wizos.loread.bean.ttrss.request; + +public class GetFeeds { + private String sid; + private String op = "getFeeds"; + + public GetFeeds(String sid) { + this.sid = sid; + } + + /** + * 0 Uncategorized + * -1 Special (e.g. Starred, Published, Archived, etc.) + * -2 Labels + * -3 All feeds, excluding virtual feeds (e.g. Labels and such) + * -4 All feeds, including virtual feeds + */ + private int cat_id = -3; + private boolean unread_only = false; + + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public int getCat_id() { + return cat_id; + } + + public void setCat_id(int cat_id) { + this.cat_id = cat_id; + } + + public boolean isUnread_only() { + return unread_only; + } + + public void setUnread_only(boolean unread_only) { + this.unread_only = unread_only; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetHeadlines.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetHeadlines.java new file mode 100644 index 0000000..c96b27d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetHeadlines.java @@ -0,0 +1,132 @@ +package me.wizos.loread.bean.ttrss.request; + +public class GetHeadlines { + private String op = "getHeadlines"; + private String sid; + /** + * all_articles, unread, adaptive, marked, updated + */ + private String view_mode = "unread"; + /** + * -1 starred + * -2 published + * -3 fresh + * -4 all articles + * 0 - archived + * IDs < -10 labels + */ + private String feed_id = "-4"; + /** + * date_reverse - oldest first + * feed_dates - newest first, goes by feed date + * (nothing) - default + */ + private String order_by = "date_reverse"; + + private int limit = 50; + private int skip; + private String since_id; + private boolean is_cat = false; + + private boolean show_content = true; + private boolean include_attachments = true; + private boolean has_sandbox = true; + + + + public String getOp() { + return op; + } + + public void setOp(String op) { + this.op = op; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public String getView_mode() { + return view_mode; + } + + public void setView_mode(String view_mode) { + this.view_mode = view_mode; + } + + public String getFeed_id() { + return feed_id; + } + + public void setFeed_id(String feed_id) { + this.feed_id = feed_id; + } + + public String getOrder_by() { + return order_by; + } + + public void setOrder_by(String order_by) { + this.order_by = order_by; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getSkip() { + return skip; + } + + public void setSkip(int skip) { + this.skip = skip; + } + + public String getSince_id() { + return since_id; + } + + public void setSince_id(String since_id) { + this.since_id = since_id; + } + + public boolean isIs_cat() { + return is_cat; + } + + public void setIs_cat(boolean is_cat) { + this.is_cat = is_cat; + } + + public boolean isShow_content() { + return show_content; + } + + public void setShow_content(boolean show_content) { + this.show_content = show_content; + } + + public boolean isInclude_attachments() { + return include_attachments; + } + + public void setInclude_attachments(boolean include_attachments) { + this.include_attachments = include_attachments; + } + + public boolean isHas_sandbox() { + return has_sandbox; + } + + public void setHas_sandbox(boolean has_sandbox) { + this.has_sandbox = has_sandbox; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetSavedItemIds.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetSavedItemIds.java new file mode 100644 index 0000000..5e7ac38 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetSavedItemIds.java @@ -0,0 +1,17 @@ +package me.wizos.loread.bean.ttrss.request; + +public class GetSavedItemIds { + private String sid; + private String op = "getSavedItemIds"; + + public GetSavedItemIds(String sid) { + this.sid = sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + public String getSid() { + return sid; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetUnreadItemIds.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetUnreadItemIds.java new file mode 100644 index 0000000..b6a26f1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/GetUnreadItemIds.java @@ -0,0 +1,17 @@ +package me.wizos.loread.bean.ttrss.request; + +public class GetUnreadItemIds { + private String sid; + private String op = "getUnreadItemIds"; + + public GetUnreadItemIds(String sid) { + this.sid = sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + public String getSid() { + return sid; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/LoginParam.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/LoginParam.java new file mode 100644 index 0000000..32c4abf --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/LoginParam.java @@ -0,0 +1,32 @@ +package me.wizos.loread.bean.ttrss.request; + +public class LoginParam { + private String user = "admin"; + private String password; + private String op = "login"; + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "login{" + + "user='" + user + '\'' + + ", password='" + password + '\'' + + ", op='" + op + '\'' + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/RequestParam.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/RequestParam.java new file mode 100644 index 0000000..62313c7 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/RequestParam.java @@ -0,0 +1,10 @@ +package me.wizos.loread.bean.ttrss.request; + +public class RequestParam { + private String op; + private String sid; + + public void setSid(String sid) { + this.sid = sid; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/SearchHeadlines.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/SearchHeadlines.java new file mode 100644 index 0000000..2e2c4fe --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/SearchHeadlines.java @@ -0,0 +1,36 @@ +package me.wizos.loread.bean.ttrss.request; + +public class SearchHeadlines { + private String op = "getHeadlines"; + private String sid; + private String view_mode = "all_articles"; + + private String search_mode = "all_feeds"; + // 搜索词 + private String search; + + /** + * -1 starred + * -2 published + * -3 fresh + * -4 all articles + * 0 - archived + * IDs < -10 labels + */ + private String feed_id = "-4"; + /** + * date_reverse - oldest first + * feed_dates - newest first, goes by feed date + * (nothing) - default + */ + private String order_by = "feed_dates"; + + private int limit = 50; + private int skip; + private String since_id; + private boolean is_cat = false; + + private boolean show_content = true; + private boolean include_attachments = true; + private boolean has_sandbox = true; +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/SearchMode.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/SearchMode.java new file mode 100644 index 0000000..57a6bfe --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/SearchMode.java @@ -0,0 +1,8 @@ +package me.wizos.loread.bean.ttrss.request; + +public enum SearchMode { + all_feeds, + this_feed, + //(category containing requested feed) + this_cat +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/SubscribeToFeed.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/SubscribeToFeed.java new file mode 100644 index 0000000..96eae19 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/SubscribeToFeed.java @@ -0,0 +1,46 @@ +package me.wizos.loread.bean.ttrss.request; + +public class SubscribeToFeed { + private String sid; + private String op = "subscribeToFeed"; + private String category_id; + private String feed_url; + + public SubscribeToFeed(String sid) { + this.sid = sid; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public String getCategory_id() { + return category_id; + } + + public void setCategory_id(String category_id) { + this.category_id = category_id; + } + + public String getFeed_url() { + return feed_url; + } + + public void setFeed_url(String feed_url) { + this.feed_url = feed_url; + } + + @Override + public String toString() { + return "SubscribeToFeed{" + + "op='" + op + '\'' + + ", sid='" + sid + '\'' + + ", category_id='" + category_id + '\'' + + ", feed_url='" + feed_url + '\'' + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/UnsubscribeFeed.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/UnsubscribeFeed.java new file mode 100644 index 0000000..b374b40 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/UnsubscribeFeed.java @@ -0,0 +1,38 @@ +package me.wizos.loread.bean.ttrss.request; + +import com.google.gson.annotations.SerializedName; + +public class UnsubscribeFeed { + private String sid; + private String op = "unsubscribeFeed"; + @SerializedName("feed_id") + private int feedId; + + public UnsubscribeFeed(String sid) { + this.sid = sid; + } + + public String getOp() { + return op; + } + + public void setOp(String op) { + this.op = op; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public int getFeedId() { + return feedId; + } + + public void setFeedId(int feedId) { + this.feedId = feedId; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/request/UpdateArticle.java b/app/src/main/java/me/wizos/loread/bean/ttrss/request/UpdateArticle.java new file mode 100644 index 0000000..7830eb8 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/request/UpdateArticle.java @@ -0,0 +1,73 @@ +package me.wizos.loread.bean.ttrss.request; + +import android.text.TextUtils; + +public class UpdateArticle { + private String sid; + private String op = "updateArticle"; + + // 如果有多个请用“,”分割 + private String article_ids; + + // 0 - starred, 1 - published, 2 - unread, 3 - article note + private int field; + + // 0 - set to false, 1 - set to true, 2 - toggle + private int mode; + + // 设置 note + private String data; + + + public UpdateArticle(String sid) { + this.sid = sid; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public String getArticle_ids() { + return article_ids; + } + + public void setArticle_ids(String article_ids) { + this.article_ids = article_ids; + } + + public void addArticle_id(String article_id) { + if (!TextUtils.isEmpty(article_ids)) { + article_ids = article_ids + "," + article_id; + } else { + article_ids = article_id; + } + } + + public int getField() { + return field; + } + + public void setField(int field) { + this.field = field; + } + + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/ArticleItem.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/ArticleItem.java new file mode 100644 index 0000000..976ed9a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/ArticleItem.java @@ -0,0 +1,214 @@ +package me.wizos.loread.bean.ttrss.result; + +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.bean.Enclosure; +import me.wizos.loread.db.Article; +import me.wizos.loread.network.api.BaseApi; +import me.wizos.loread.utils.ArticleUtil; + +public class ArticleItem { + private int id; + private String guid; + + private boolean unread; + private boolean marked; + private boolean published; + + private long updated; + private boolean is_updated; + + private String title; + private String link; + private String author; + private String content; + + private List attachments; + private List tags; + private List labels; + private String comments_link; + private int comments_count; + + private String feed_id; + private String feed_title; + + private String flavor_image; + private String flavor_stream; + private String lang = "zh"; + private String note = ""; + + + private int score; + private boolean always_display_attachments; + + + public Article convert(BaseApi.ArticleChanger articleChanger) { + Article article = new Article(); + article.setId(String.valueOf(id)); + title = ArticleUtil.getOptimizedTitle(title); + article.setTitle(title); + + article.setAuthor(author); + article.setPubDate(updated * 1000); + + article.setLink(link); + article.setFeedId(feed_id); + article.setFeedTitle(feed_title); + + String tmpContent = ArticleUtil.getOptimizedContent(article.getLink(), content); + tmpContent = ArticleUtil.getOptimizedContentWithEnclosures(tmpContent,attachments); + article.setContent(tmpContent); + + String tmpSummary = ArticleUtil.getOptimizedSummary(tmpContent); + article.setSummary(tmpSummary); + + String coverUrl = ArticleUtil.getCoverUrl(article.getLink(),tmpContent); + article.setImage(coverUrl); + + // 自己设置的字段 + // KLog.i("【增加文章】" + article.getId()); + article.setSaveStatus(App.STATUS_NOT_FILED); + if (unread) { + article.setReadStatus(App.STATUS_UNREAD); + } else { + article.setReadStatus(App.STATUS_READED); + } + if (marked) { + article.setStarStatus(App.STATUS_STARED); + } else { + article.setStarStatus(App.STATUS_UNSTAR); + } + + if (articleChanger != null) { + articleChanger.change(article); + } + return article; + } + + @Override + public String toString() { + return "TTRSSArticleItem{" + + "id=" + id + + ", guid='" + guid + '\'' + + ", unread=" + unread + + ", marked=" + marked + + ", published=" + published + + ", updated=" + updated + + ", is_updated=" + is_updated + + ", title='" + title + '\'' + + ", link='" + link + '\'' + + ", author='" + author + '\'' + + ", content='" + content + '\'' + + ", attachments=" + attachments + + ", tags=" + tags + + ", labels=" + labels + + ", comments_link='" + comments_link + '\'' + + ", comments_count=" + comments_count + + ", feed_id='" + feed_id + '\'' + + ", feed_title='" + feed_title + '\'' + + ", flavor_image='" + flavor_image + '\'' + + ", flavor_stream='" + flavor_stream + '\'' + + ", lang='" + lang + '\'' + + ", note='" + note + '\'' + + ", score=" + score + + ", always_display_attachments=" + always_display_attachments + + '}'; + } + + public int getId() { + return id; + } + + public String getGuid() { + return guid; + } + + public boolean isUnread() { + return unread; + } + + public boolean isMarked() { + return marked; + } + + public boolean isPublished() { + return published; + } + + public long getUpdated() { + return updated; + } + + public boolean isIs_updated() { + return is_updated; + } + + public String getTitle() { + return title; + } + + public String getLink() { + return link; + } + + public String getAuthor() { + return author; + } + + public String getContent() { + return content; + } + + public List getAttachments() { + return attachments; + } + + public List getTags() { + return tags; + } + + public List getLabels() { + return labels; + } + + public String getComments_link() { + return comments_link; + } + + public int getComments_count() { + return comments_count; + } + + public String getFeed_id() { + return feed_id; + } + + public String getFeed_title() { + return feed_title; + } + + public String getFlavor_image() { + return flavor_image; + } + + public String getFlavor_stream() { + return flavor_stream; + } + + public String getLang() { + return lang; + } + + public String getNote() { + return note; + } + + public int getScore() { + return score; + } + + public boolean isAlways_display_attachments() { + return always_display_attachments; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/Attachment.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/Attachment.java new file mode 100644 index 0000000..e6acc6a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/Attachment.java @@ -0,0 +1,14 @@ +//package me.wizos.loreadx.bean.ttrss.result; +// +//public class Attachment { +// private String content_url; +// private String content_type; +// +// public String getContent_url() { +// return content_url; +// } +// +// public String getContent_type() { +// return content_type; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/CategoryItem.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/CategoryItem.java new file mode 100644 index 0000000..bb1a2f3 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/CategoryItem.java @@ -0,0 +1,68 @@ +package me.wizos.loread.bean.ttrss.result; + +import com.google.gson.annotations.SerializedName; + +import me.wizos.loread.db.Category; + +public class CategoryItem { + @SerializedName("id") + private String id; + @SerializedName("title") + private String title; + + private int unread; + private int order_id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getUnread() { + return unread; + } + + public void setUnread(int unread) { + this.unread = unread; + } + + public int getOrder_id() { + return order_id; + } + + public void setOrder_id(int order_id) { + this.order_id = order_id; + } + + + public Category convert() { + Category category = new Category(); + category.setId(id); + category.setTitle(title); +// category.setId( "user/" + id); +// category.setUnreadCount(unread); + return category; + } + + + @Override + public String toString() { + return "TTRSSCategoryItem{" + + "id='" + id + '\'' + + ", title='" + title + '\'' + + ", unread=" + unread + + ", order_id=" + order_id + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/FeedItem.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/FeedItem.java new file mode 100644 index 0000000..fe9bbf4 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/FeedItem.java @@ -0,0 +1,110 @@ +package me.wizos.loread.bean.ttrss.result; + +import com.google.gson.annotations.SerializedName; + +import me.wizos.loread.App; +import me.wizos.loread.db.Feed; + +public class FeedItem { + @SerializedName("id") + private int id; + @SerializedName("title") + private String title; + @SerializedName("feed_url") + private String feedUrl; + @SerializedName("site_url") + private String siteUrl; + @SerializedName("unread") + private int unread; + @SerializedName("cat_id") + private int catId; + @SerializedName("order_id") + private int orderId; + @SerializedName("last_updated") + private long lastUpdated; + @SerializedName("has_icon") + private boolean hasIcon; + + + public String getFeedUrl() { + return feedUrl; + } + public void setFeedUrl(String feedUrl) { + this.feedUrl = feedUrl; + } + public String getSiteUrl() { + return siteUrl; + } + public void setSiteUrl(String siteUrl) { + this.siteUrl = siteUrl; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public int getUnread() { + return unread; + } + public void setUnread(int unread) { + this.unread = unread; + } + public int getCatId() { + return catId; + } + public void setCatId(int catId) { + this.catId = catId; + } + public int getOrderId() { + return orderId; + } + public void setOrderId(int orderId) { + this.orderId = orderId; + } + public long getLastUpdated() { + return lastUpdated; + } + public void setLastUpdated(long lastUpdated) { + this.lastUpdated = lastUpdated; + } + public boolean isHasIcon() { + return hasIcon; + } + public void setHasIcon(boolean hasIcon) { + this.hasIcon = hasIcon; + } + + + public Feed convert2Feed() { + Feed feed = new Feed(); + feed.setId(String.valueOf(id)); + feed.setTitle(title); + feed.setFeedUrl(feedUrl); + feed.setHtmlUrl(siteUrl); + //feed.setIconUrl(visualUrl); + feed.setDisplayMode(App.OPEN_MODE_RSS); + return feed; + } + + @Override + public String toString() { + return "TTRSSFeedItem{" + + "feed_url='" + feedUrl + '\'' + + ", site_url='" + siteUrl + '\'' + + ", title='" + title + '\'' + + ", id=" + id + + ", unread=" + unread + + ", cat_id=" + catId + + ", order_id=" + orderId + + ", last_updated=" + lastUpdated + + ", has_icon=" + hasIcon + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/SubscribeToFeedResult.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/SubscribeToFeedResult.java new file mode 100644 index 0000000..28c8679 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/SubscribeToFeedResult.java @@ -0,0 +1,6 @@ +package me.wizos.loread.bean.ttrss.result; + +public class SubscribeToFeedResult { + private int code; + private int feed_id; +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/TTRSSLoginResult.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/TTRSSLoginResult.java new file mode 100644 index 0000000..7fb1c4a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/TTRSSLoginResult.java @@ -0,0 +1,22 @@ +package me.wizos.loread.bean.ttrss.result; + +public class TTRSSLoginResult { + private String session_id; + private int api_level; + + public String getSession_id() { + return session_id; + } + + public void setSession_id(String session_id) { + this.session_id = session_id; + } + + public int getApi_level() { + return api_level; + } + + public void setApi_level(int api_level) { + this.api_level = api_level; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/TinyResponse.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/TinyResponse.java new file mode 100644 index 0000000..776180d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/TinyResponse.java @@ -0,0 +1,63 @@ +package me.wizos.loread.bean.ttrss.result; + +import com.google.gson.annotations.SerializedName; + +public class TinyResponse { + private int seq; + private int status; + @SerializedName(value = "msg", alternate = {"error"}) + private String msg; + private T content; + + public boolean isSuccessful() { + if (status == 0) { + //KLog.i("请求正常"); + return true; + } + //KLog.i("请求异常:" + content); + return false; + } + + public int getSeq() { + return seq; + } + + public void setSeq(int seq) { + this.seq = seq; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public T getContent() { + return content; + } + + public void setContent(T content) { + this.content = content; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + + @Override + public String toString() { + return "TTRSSResponse{" + + "seq=" + seq + + ", status=" + status + + ", msg='" + msg + '\'' + + ", content=" + content + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/bean/ttrss/result/UpdateArticleResult.java b/app/src/main/java/me/wizos/loread/bean/ttrss/result/UpdateArticleResult.java new file mode 100644 index 0000000..8633b59 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bean/ttrss/result/UpdateArticleResult.java @@ -0,0 +1,22 @@ +package me.wizos.loread.bean.ttrss.result; + +public class UpdateArticleResult { + private String status; + private int updated; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getUpdated() { + return updated; + } + + public void setUpdated(int updated) { + this.updated = updated; + } +} diff --git a/app/src/main/java/me/wizos/loread/behavior/BottomNavigationBehavior.java b/app/src/main/java/me/wizos/loread/behavior/BottomNavigationBehavior.java new file mode 100644 index 0000000..a1f7175 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/behavior/BottomNavigationBehavior.java @@ -0,0 +1,52 @@ +package me.wizos.loread.behavior; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; + +/** + * 不需要与toolbar联动,即可隐藏底部菜单 + * + * @link https://blog.csdn.net/Keepsty/article/details/81740663 + * @date 2019/2/3. + */ + +public class BottomNavigationBehavior extends CoordinatorLayout.Behavior { + private ObjectAnimator outAnimator, inAnimator; + + public BottomNavigationBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + // 垂直滑动 + @Override + public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; + } + + + @Override + public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { + if (dy > 0) {// 上滑隐藏 + if (outAnimator == null) { + outAnimator = ObjectAnimator.ofFloat(child, "translationY", 0, child.getHeight()); + outAnimator.setDuration(200); + } + if (!outAnimator.isRunning() && child.getTranslationY() <= 0) { + outAnimator.start(); + } + } else if (dy < 0) {// 下滑显示 + if (inAnimator == null) { + inAnimator = ObjectAnimator.ofFloat(child, "translationY", child.getHeight(), 0); + inAnimator.setDuration(200); + } + if (!inAnimator.isRunning() && child.getTranslationY() >= child.getHeight()) { + inAnimator.start(); + } + } + } +} diff --git a/app/src/main/java/me/wizos/loread/behavior/BottomNavigationViewBehavior.java b/app/src/main/java/me/wizos/loread/behavior/BottomNavigationViewBehavior.java index 5966a0d..c62f53c 100644 --- a/app/src/main/java/me/wizos/loread/behavior/BottomNavigationViewBehavior.java +++ b/app/src/main/java/me/wizos/loread/behavior/BottomNavigationViewBehavior.java @@ -17,12 +17,14 @@ package me.wizos.loread.behavior; import android.content.Context; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.view.View; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; + +import com.google.android.material.appbar.AppBarLayout; + /** * 与toolbar联动隐藏底部菜单 * @@ -32,6 +34,7 @@ */ public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior { public BottomNavigationViewBehavior() { + super(); } public BottomNavigationViewBehavior(Context context, AttributeSet attrs) { diff --git a/app/src/main/java/me/wizos/loread/common/ImageBridge.java b/app/src/main/java/me/wizos/loread/bridge/ArticleBridge.java similarity index 66% rename from app/src/main/java/me/wizos/loread/common/ImageBridge.java rename to app/src/main/java/me/wizos/loread/bridge/ArticleBridge.java index 110203a..f2a576d 100644 --- a/app/src/main/java/me/wizos/loread/common/ImageBridge.java +++ b/app/src/main/java/me/wizos/loread/bridge/ArticleBridge.java @@ -1,4 +1,4 @@ -package me.wizos.loread.common; +package me.wizos.loread.bridge; /** * 设置图片的默认加载行为 @@ -16,7 +16,8 @@ * @author by Wizos on 2018/3/4. */ -public interface ImageBridge { +public interface ArticleBridge { + String TAG = "ArticleBridge"; void log(String paramString); @@ -29,18 +30,19 @@ public interface ImageBridge { * 3,关闭省流量 & 蜂窝模式 → 返回正在下载占位图,开始下载 * 3,关闭省流量 & Wifi模式 → 返回正在下载占位图,开始下载 */ - void loadImage(String articleId, int index, String url, String originalUrl); + void readImage(String articleId,String imgHashCode, String originalUrl); + //void loadImage(String articleId,String imgHashCode, String url, String originalUrl); - void downImage(String articleId, int index, final String originalUrl); + //void downImage(String articleId,String imgHashCode, String originalUrl); - void openImage(String articleId, String urls, int index); + void downImage(String articleId,String imgHashCode, String originalUrl, boolean guessReferer); + + void openImage(String articleId, String url); void openLink(String link); - void tryInitJs(String articleId); + void openAudio(String link); void readability(); - // 切换屏幕方向,为播放iframe视频而用 -// void toggleScreenOrientation(); } diff --git a/app/src/main/java/me/wizos/loread/bridge/WebBridge.java b/app/src/main/java/me/wizos/loread/bridge/WebBridge.java new file mode 100644 index 0000000..6581e19 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/bridge/WebBridge.java @@ -0,0 +1,7 @@ +package me.wizos.loread.bridge; + +public interface WebBridge { + String TAG = "WebBridge"; + void log(String msg); + void toggleScreenOrientation(); +} diff --git a/app/src/main/java/me/wizos/loread/view/webview/AdBlock.java b/app/src/main/java/me/wizos/loread/config/AdBlock.java similarity index 68% rename from app/src/main/java/me/wizos/loread/view/webview/AdBlock.java rename to app/src/main/java/me/wizos/loread/config/AdBlock.java index c82f09d..80add11 100644 --- a/app/src/main/java/me/wizos/loread/view/webview/AdBlock.java +++ b/app/src/main/java/me/wizos/loread/config/AdBlock.java @@ -1,13 +1,16 @@ -package me.wizos.loread.view.webview; +package me.wizos.loread.config; -import android.content.res.AssetManager; +import android.annotation.SuppressLint; +import android.net.Uri; import android.os.AsyncTask; +import com.socks.library.KLog; + import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.net.URI; -import java.net.URISyntaxException; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -16,25 +19,26 @@ public class AdBlock { - private static AdBlock adBlock; - private static final String FILE = "hosts.txt"; + private transient static AdBlock instance; + private static final String FILE = "ad_block.txt"; private static final Set hosts = new HashSet<>(); + @SuppressLint("ConstantLocale") private static final Locale locale = Locale.getDefault(); - - private AdBlock() { - } - + private AdBlock() {} public static AdBlock i() { - if (adBlock == null) { + if (instance == null) { synchronized (AdBlock.class) { - if (adBlock == null) { - adBlock = new AdBlock(); + if (instance == null) { + instance = new AdBlock(); loadHosts(); } } } - return adBlock; + return instance; + } + public void reset() { + instance = null; } @@ -42,31 +46,34 @@ private static void loadHosts() { AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override public void run() { - AssetManager manager = App.i().getAssets(); try { - BufferedReader reader = new BufferedReader(new InputStreamReader(manager.open(FILE))); + if( !new File(App.i().getGlobalAssetsFilesDir() + FILE).exists() ){ + return; + } + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(App.i().getGlobalConfigPath() + "ad_block.txt"))); String line; while ((line = reader.readLine()) != null) { hosts.add(line.toLowerCase(locale)); } } catch (IOException i) { + i.printStackTrace(); + KLog.i(i); } } }); } - private static String getDomain(String url) throws URISyntaxException { - url = url.toLowerCase(locale); - - int index = url.indexOf('/', 8); // -> http://(7) and https://(8) - if (index != -1) { - url = url.substring(0, index); - } - - URI uri = new URI(url); + private static String getDomain(String url) { + Uri uri = Uri.parse(url); String domain = uri.getHost(); + if (domain == null) { + url = url.toLowerCase(locale); + int index = url.indexOf('/', 8); // -> http://(7) and https://(8) + if (index != -1) { + url = url.substring(0, index); + } return url; } return domain.startsWith("www.") ? domain.substring(4) : domain; @@ -74,13 +81,7 @@ private static String getDomain(String url) throws URISyntaxException { public boolean isAd(String url) { - String domain; - try { - domain = getDomain(url); - } catch (URISyntaxException u) { - return false; - } - return hosts.contains(domain.toLowerCase(locale)); + return hosts.contains(getDomain(url).toLowerCase(locale)); } diff --git a/app/src/main/java/me/wizos/loread/config/ArticleActionConfig.java b/app/src/main/java/me/wizos/loread/config/ArticleActionConfig.java new file mode 100644 index 0000000..011f4d1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/ArticleActionConfig.java @@ -0,0 +1,259 @@ +package me.wizos.loread.config; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import androidx.sqlite.db.SimpleSQLiteQuery; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.socks.library.KLog; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import me.wizos.loread.App; +import me.wizos.loread.config.article_action_rule.ArticleActionRule; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Entry; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; + +public class ArticleActionConfig { + private static ArticleActionConfig instance; + private ArticleActionConfig() { } + public static ArticleActionConfig i() { + if (instance == null) { + synchronized (ArticleActionConfig.class) { + if (instance == null) { + instance = new ArticleActionConfig(); + String json = FileUtil.readFile(App.i().getUserConfigPath() + "article_action_rule.json"); + if (TextUtils.isEmpty(json)) { + instance.actionRuleArrayMap = new ArrayMap(); + instance.save(); + }else { + instance.actionRuleArrayMap = new Gson().fromJson(json, new TypeToken>() {}.getType()); + } + } + } + } + return instance; + } + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "article_action_rule.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance.actionRuleArrayMap)); + } + public void reset() { + instance = null; + } + + + private ArrayMap actionRuleArrayMap; + + + public void exeRules(String uid, long timeMillis){ + // 1.执行规则 + for (Map.Entry entry: actionRuleArrayMap.entrySet()) { + exeRule(uid, entry.getValue(), timeMillis); + } + } + private void exeRule(String uid, ArticleActionRule articleActionRule, long timeMillis){ + KLog.e("用户:"+ uid + " , " + articleActionRule); + if("all".equals(articleActionRule.getTarget())){ + String sql = ""; + if("contain".equals(articleActionRule.getJudge())){ + String[] keywords = articleActionRule.getValue().split("\\|"); + Set conditions = new HashSet<>(); + for (String keyword:keywords) { + conditions.add( articleActionRule.getAttr() + " like '%" + keyword + "%'"); + } + sql = StringUtils.join(" or ", conditions); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM article WHERE uid = ? AND crawlDate >= ? AND " + sql, new Object[]{uid,timeMillis}); + List
articles = CoreDB.i().articleDao().getActionRuleArticlesRaw(query); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + + }else if("not contain".equals(articleActionRule.getJudge())){ + String[] keywords = articleActionRule.getValue().split("\\|"); + Set conditions = new HashSet<>(); + for (String keyword:keywords) { + conditions.add( articleActionRule.getAttr() + " not like '%" + keyword + "%'"); + } + sql = StringUtils.join(" and ", conditions); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM article WHERE uid = ? AND crawlDate >= ? AND " + sql, new Object[]{uid,timeMillis}); + List
articles = CoreDB.i().articleDao().getActionRuleArticlesRaw(query); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + }else if("match".equals(articleActionRule.getJudge())){ + List needActionArticleIds = new ArrayList<>(); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT id, " + articleActionRule.getAttr() + " as entry FROM article WHERE uid = ? AND crawlDate >= ?" + sql, new Object[]{uid,timeMillis}); + List entries = CoreDB.i().articleDao().getActionRuleArticlesRaw2(query); + Iterator iterator = entries.iterator(); + Pattern pattern = Pattern.compile(articleActionRule.getValue(), Pattern.CASE_INSENSITIVE); + Entry entry; + while (iterator.hasNext()){ + entry = iterator.next(); + if(pattern.matcher( entry.getEntry() ).find()){ + needActionArticleIds.add(entry.getId()); + iterator.remove(); + } + } + List
articles = CoreDB.i().articleDao().getArticles(uid, needActionArticleIds); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + }else if("not match".equals(articleActionRule.getJudge())){ + List needActionArticleIds = new ArrayList<>(); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT id, " + articleActionRule.getAttr() + " as entry FROM article WHERE uid = ? AND crawlDate >= ?" + sql, new Object[]{uid,timeMillis}); + List entries = CoreDB.i().articleDao().getActionRuleArticlesRaw2(query); + Iterator iterator = entries.iterator(); + Pattern pattern = Pattern.compile(articleActionRule.getValue(), Pattern.CASE_INSENSITIVE); + Entry entry; + while (iterator.hasNext()){ + entry = iterator.next(); + if(!pattern.matcher( entry.getEntry() ).find()){ + needActionArticleIds.add(entry.getId()); + iterator.remove(); + } + } + List
articles = CoreDB.i().articleDao().getArticles(uid, needActionArticleIds); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + } + }else if(articleActionRule.getTarget().startsWith("feed/")){ + String feedUrl = articleActionRule.getTarget().substring(5); + KLog.e("被处理的feedUrl为:" + feedUrl); + String sql = ""; + if("contain".equals(articleActionRule.getJudge())){ + String[] keywords = articleActionRule.getValue().split("\\|"); + Set conditions = new HashSet<>(); + for (String keyword:keywords) { + conditions.add( "article." + articleActionRule.getAttr() + " like '%" + keyword + "%'"); + } + sql = StringUtils.join(" or ", conditions); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT article.* FROM article LEFT JOIN Feed ON (article.uid = Feed.uid AND article.feedId = Feed.id) WHERE article.uid = ? AND crawlDate >= ? AND Feed.feedUrl = ? AND " + sql, new Object[]{uid, timeMillis, feedUrl}); + List
articles = CoreDB.i().articleDao().getActionRuleArticlesRaw(query); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + }else if("not contain".equals(articleActionRule.getJudge())){ + String[] keywords = articleActionRule.getValue().split("\\|"); + Set conditions = new HashSet<>(); + for (String keyword:keywords) { + conditions.add( "article." + articleActionRule.getAttr() + " not like '%" + keyword + "%'"); + } + sql = StringUtils.join(" and ", conditions); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT article.* FROM article LEFT JOIN Feed ON (article.uid = Feed.uid AND article.feedId = Feed.id) WHERE article.uid = ? AND crawlDate >= ? AND Feed.feedUrl = ? AND " + sql, new Object[]{uid,timeMillis, feedUrl}); + List
articles = CoreDB.i().articleDao().getActionRuleArticlesRaw(query); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + }else if("match".equals(articleActionRule.getJudge())){ + List needActionArticleIds = new ArrayList<>(); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT article.id, article." + articleActionRule.getAttr() + " as entry FROM article LEFT JOIN Feed ON (article.uid = Feed.uid AND article.feedId = Feed.id) WHERE article.uid = ? AND crawlDate >= ? AND Feed.feedUrl = ? " + sql, new Object[]{App.i().getUser().getId(),timeMillis, feedUrl}); + List entries = CoreDB.i().articleDao().getActionRuleArticlesRaw2(query); + Iterator iterator = entries.iterator(); + Pattern pattern = Pattern.compile(articleActionRule.getValue(), Pattern.CASE_INSENSITIVE); + Entry entry; + while (iterator.hasNext()){ + entry = iterator.next(); + if(pattern.matcher( entry.getEntry() ).find()){ + needActionArticleIds.add(entry.getId()); + iterator.remove(); + } + } + List
articles = CoreDB.i().articleDao().getArticles(uid, needActionArticleIds); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + }else if("not match".equals(articleActionRule.getJudge())){ + List needActionArticleIds = new ArrayList<>(); + SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT article.id, article." + articleActionRule.getAttr() + " as entry FROM article LEFT JOIN Feed ON (article.uid = Feed.uid AND article.feedId = Feed.id) WHERE article.uid = ? AND crawlDate >= ? AND Feed.feedUrl = ? " + sql, new Object[]{uid,timeMillis, feedUrl}); + List entries = CoreDB.i().articleDao().getActionRuleArticlesRaw2(query); + Iterator iterator = entries.iterator(); + Pattern pattern = Pattern.compile(articleActionRule.getValue(), Pattern.CASE_INSENSITIVE); + Entry entry; + while (iterator.hasNext()){ + entry = iterator.next(); + if(!pattern.matcher( entry.getEntry() ).find()){ + needActionArticleIds.add(entry.getId()); + iterator.remove(); + } + } + List
articles = CoreDB.i().articleDao().getArticles(uid, needActionArticleIds); + doActionWithArticles(articles, articleActionRule); + KLog.e("文章结果 为:" + query.getSql() + " == " + articles.size()); + } + } + } + + private void doActionWithArticles(List
articles, ArticleActionRule articleActionRule){ + if(articleActionRule ==null || articleActionRule.getActions() == null || articleActionRule.getActions().size() == 0){ + return; + } + if(articleActionRule.getActions().contains("mark read")){ + List articleIds = new ArrayList<>(); + for (Article article:articles) { + article.setReadStatus(App.STATUS_READED); + articleIds.add(article.getId()); + } + App.i().getApi().markArticleListReaded(articleIds, new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + } + }); + CoreDB.i().articleDao().update(articles); + } + + if(articleActionRule.getActions().contains("mark unreading")){ + for (Article article:articles) { + article.setReadStatus(App.STATUS_UNREADING); + } + CoreDB.i().articleDao().update(articles); + } + + if(articleActionRule.getActions().contains("mark star")){ + for (Article article:articles) { + article.setReadStatus(App.STATUS_STARED); + App.i().getApi().markArticleStared(article.getId(), new CallbackX() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Object error) { + } + }); + } + CoreDB.i().articleDao().update(articles); + } + + if(articleActionRule.getActions().contains("delete")){ + CoreDB.i().articleDao().delete(articles); + } + } + + + + private List
getNeedActionArticlesWithNotMatch(List
articles, Pattern pattern, String input){ + Article article; + Iterator
iterator = articles.iterator(); + List
needActionArticles = new ArrayList<>(); + while (iterator.hasNext()){ + article = iterator.next(); + if(!pattern.matcher( input ).find()){ + needActionArticles.add(article); + iterator.remove(); + } + } + return needActionArticles; + } + +} diff --git a/app/src/main/java/me/wizos/loread/config/ArticleExtractConfig.java b/app/src/main/java/me/wizos/loread/config/ArticleExtractConfig.java new file mode 100644 index 0000000..542bf75 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/ArticleExtractConfig.java @@ -0,0 +1,136 @@ +package me.wizos.loread.config; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; +import com.socks.library.KLog; + +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.wizos.loread.App; +import me.wizos.loread.config.article_extract_rule.ArticleExtractRule; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; + +public class ArticleExtractConfig { + private transient static ArticleExtractConfig instance; + private ArticleExtractConfig() { } + public static ArticleExtractConfig i() { + if (instance == null) { + synchronized (ArticleExtractConfig.class) { + if (instance == null) { + instance = new ArticleExtractConfig(); + String json = FileUtil.readFile(App.i().getUserConfigPath() + "article_extract_rule.json"); + if (TextUtils.isEmpty(json)) { + instance.pageMatchRegex = new ArrayMap(); + instance.pageMatchCssSelector = new ArrayMap(); + instance.save(); + }else { + instance = new Gson().fromJson(json, ArticleExtractConfig.class); + } + } + } + } + return instance; + } + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "article_extract_rule.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance)); + } + public void reset() { + instance = null; + } + + + @SerializedName("page_match_css_selector") + private ArrayMap pageMatchCssSelector; + @SerializedName("page_match_regex") + private ArrayMap pageMatchRegex; + + public ArticleExtractRule getRuleByDomain(String domain){ + String rules = FileUtil.readFile( App.i().getUserConfigPath() + "article_extract_rule/" + domain + ".json"); + KLog.e("获取到的抓取规则内容:" + domain + " == " + rules); + if (!StringUtils.isEmpty(rules)) { + return new Gson().fromJson(rules, ArticleExtractRule.class); + } + return null; + } + +// public void invalidRuleByDomain(String domain){ +// File file = new File(App.i().getUserConfigPath() + "article_extract_rule/" + domain + ".json"); +// if(file.exists()){ +// file.renameTo(new File(App.i().getUserConfigPath() + "article_extract_rule_invalid/" + domain + ".json")); +// } +// } + + public void saveRuleByDomain(Document document, String domain, String oriCssSelector){ + String optimizedCssSelector = optimizeCSSSelector(oriCssSelector); + if (document.select(optimizedCssSelector).size() == 1) { + saveSiteRule(domain, optimizedCssSelector); + } else { + saveSiteRule(domain, oriCssSelector); + } + } + private static final String RE_RULE1 = " *(div|post|entry|article)(\\.[A-z0-9-_]+)*([.#])(entry|post|article)([-_])(content|article|body)([. ]|$)"; + private static final String RE_RULE2 = " *(div|post|entry|article)(\\.[A-z0-9-_]+)*([.#])(entry|post|article|content|body)([. ]|$)"; + private static String optimizeCSSSelector(String cssQuery) { + Pattern pattern = Pattern.compile(RE_RULE1, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(cssQuery); + if (matcher.find()) { + return matcher.group(1) + matcher.group(3) + matcher.group(4) + matcher.group(5) + matcher.group(6); + } + pattern = Pattern.compile(RE_RULE2, Pattern.CASE_INSENSITIVE); + matcher = pattern.matcher(cssQuery); + if (matcher.find()) { + return matcher.group(1) + matcher.group(3) + matcher.group(4); + } + return cssQuery; + } + + private static void saveSiteRule(String domain, String cssSelector) { + ArticleExtractRule articleExtractRule = new ArticleExtractRule(); + articleExtractRule.setContent(cssSelector); + + Gson gson = new GsonBuilder() + .setPrettyPrinting() //对结果进行格式化,增加换行 + .disableHtmlEscaping() //避免Gson使用时将一些字符自动转换为Unicode转义字符 + .create(); + FileUtil.save(App.i().getUserConfigPath() + "article_extract_rule/" + domain + "_new.json", gson.toJson(articleExtractRule, ArticleExtractRule.class)); + } + + + public ArticleExtractRule getRuleByCssSelector(Document document){ + if(pageMatchCssSelector == null || document == null){ + return null; + } + Elements elements; + for (Map.Entry entry:pageMatchCssSelector.entrySet()) { + elements = document.select(entry.getKey()); + if(elements != null && elements.size() > 0) { + return entry.getValue(); + } + } + return null; + } + + public ArticleExtractRule getRuleByRegex(String page){ + if(pageMatchRegex == null || StringUtils.isEmpty(page)){ + return null; + } + Pattern pattern; + for (Map.Entry entry:pageMatchRegex.entrySet()) { + pattern = Pattern.compile(entry.getKey(),Pattern.CASE_INSENSITIVE); + if(pattern.matcher(page).find()){ + return entry.getValue(); + } + } + return null; + } +} diff --git a/app/src/main/java/me/wizos/loread/config/ArticleTags.java b/app/src/main/java/me/wizos/loread/config/ArticleTags.java new file mode 100644 index 0000000..254d56c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/ArticleTags.java @@ -0,0 +1,83 @@ +package me.wizos.loread.config; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import me.wizos.loread.App; +import me.wizos.loread.db.ArticleTag; +import me.wizos.loread.utils.FileUtil; + +/** + * 文章保存目录: + * 1.默认目录(无分类) + * 2.订阅源 + * 3.订阅源所属的分类(分类可能有多个,如何确定) + * 4.保存时手动配置 + * + * 自定义分类 + * @author Wizos on 2020/6/14. + */ +public class ArticleTags { + private static ArticleTags instance; + private ArticleTags() { } + public static ArticleTags i() { + if (instance == null) { + synchronized (ArticleTags.class) { + if (instance == null) { + Gson gson = new Gson(); + String config = FileUtil.readFile(App.i().getUserFilesDir() + "/config/article_tags.json"); + if (TextUtils.isEmpty(config)) { + instance = new ArticleTags(); + instance.articleTags = new ArrayMap<>(); + instance.tags = new HashSet<>(); + } else { + instance = gson.fromJson(config, ArticleTags.class); + } + } + } + } + return instance; + } + public void reset() { + instance = null; + } + public void save() { + FileUtil.save(App.i().getUserFilesDir() + "/config/article_tags.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance)); + } + + + private ArrayMap> articleTags; + private Set tags; + public void removeArticle(String articleId){ + articleTags.remove(articleId); + } + public void addArticleTags(List articleTags){ + for (ArticleTag articleTag:articleTags) { + addArticleTag(articleTag); + } + } + public void addArticleTag(ArticleTag articleTag){ + if(articleTag != null){ + Set tags; + if(articleTags.containsKey(articleTag.getArticleId())){ + tags = articleTags.get(articleTag.getArticleId()); + }else { + tags = new HashSet<>(); + } + tags.add(articleTag.getTagId()); + this.tags.add(articleTag.getTagId()); + articleTags.put(articleTag.getArticleId(),tags); + } + } + + public void newTag(String directory){ + tags.add(directory); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/config/HostConfig.java b/app/src/main/java/me/wizos/loread/config/HostConfig.java new file mode 100644 index 0000000..c32b774 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/HostConfig.java @@ -0,0 +1,58 @@ +//package me.wizos.loread.config; +// +//import android.net.Uri; +//import android.text.TextUtils; +//import android.util.ArrayMap; +// +//import com.google.gson.Gson; +//import com.google.gson.GsonBuilder; +//import com.google.gson.reflect.TypeToken; +// +//import me.wizos.loread.App; +//import me.wizos.loread.utils.FileUtil; +// +///** +// * @author Wizos on 2020/4/14. +// */ +//public class HostConfig { +// private HostConfig() { } +// public static HostConfig i() { +// if (instance == null) { +// synchronized (HostConfig.class) { +// if (instance == null) { +// Gson gson = new Gson(); +// instance = new HostConfig(); +// +// String config = FileUtil.readFile(App.i().getGlobalConfigPath() + "host_rewrite.json"); +// if (TextUtils.isEmpty(config)) { +// instance.domainRewrite = new ArrayMap<>(); +// } else { +// instance.domainRewrite = gson.fromJson(config, new TypeToken>() {}.getType()); +// } +// } +// } +// } +// return instance; +// } +// public void reset() { +// instance = null; +// } +// public void save() { +// FileUtil.save(App.i().getGlobalConfigPath() + "host_rewrite.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance.domainRewrite)); +// } +// +// private static HostConfig instance; +// private ArrayMap domainRewrite; +// +// //@SerializedName("url_match_domain_rewrite_url") +// +// public String getRedirectUrl(String url) { +// Uri uri = Uri.parse(url); +// String host = uri.getHost(); +// if (domainRewrite.containsKey(host)) { +// assert host != null; +// return url.replaceFirst(host, domainRewrite.get(host)); +// } +// return ""; +// } +//} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/config/LinkRewriteConfig.java b/app/src/main/java/me/wizos/loread/config/LinkRewriteConfig.java new file mode 100644 index 0000000..1596f43 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/LinkRewriteConfig.java @@ -0,0 +1,72 @@ +package me.wizos.loread.config; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; +import com.socks.library.KLog; + +import javax.script.Bindings; +import javax.script.SimpleBindings; + +import me.wizos.loread.App; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.ScriptUtil; + +/** + * @author Wizos on 2020/4/14. + */ +public class LinkRewriteConfig { + private LinkRewriteConfig() { } + public static LinkRewriteConfig i() { + if (instance == null) { + synchronized (LinkRewriteConfig.class) { + if (instance == null) { + Gson gson = new Gson(); + + String config = FileUtil.readFile(App.i().getUserConfigPath() + "link_rewrite.json"); + if (TextUtils.isEmpty(config)) { + instance = new LinkRewriteConfig(); + instance.domainRewrite = new ArrayMap<>(); + instance.urlRewrite = new ArrayMap<>(); + } else { + instance = gson.fromJson(config, LinkRewriteConfig.class); + } + } + } + } + return instance; + } + public void reset() { + instance = null; + } + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "link_rewrite.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance)); + } + + @SerializedName("url_match_domain_rewrite_domain") + private ArrayMap domainRewrite; + + @SerializedName("url_match_domain_rewrite_url") + private ArrayMap urlRewrite; + private static LinkRewriteConfig instance; + + public String getRedirectUrl(String url) { + Uri uri = Uri.parse(url); + String host = uri.getHost(); + if (domainRewrite.containsKey(host)) { + return url.replaceFirst(host, domainRewrite.get(host)); + } else if (urlRewrite.containsKey(host)) { + // Bindings接口可以理解为上下文,可以往上下文中设置一个Java对象或通过key获取一个对象,它有一个实现类,SimpleBindings,内部就是一个map。 + Bindings bindings = new SimpleBindings(); + bindings.put("url", url); + ScriptUtil.i().eval(urlRewrite.get(host), bindings); + KLog.i("重定向JS:" + urlRewrite.get(host)); + return (String) bindings.get("url"); + } + return ""; + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/config/NetworkRefererConfig.java b/app/src/main/java/me/wizos/loread/config/NetworkRefererConfig.java new file mode 100644 index 0000000..9dd2c48 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/NetworkRefererConfig.java @@ -0,0 +1,130 @@ +package me.wizos.loread.config; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import java.util.Arrays; + +import me.wizos.loread.App; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; + +public class NetworkRefererConfig { + private transient static NetworkRefererConfig instance; + public static NetworkRefererConfig i() { + if (instance == null) { + synchronized (NetworkRefererConfig.class) { + if (instance == null) { + String json = FileUtil.readFile(App.i().getUserConfigPath() + "network_referer.json"); + instance = new NetworkRefererConfig(); + if (TextUtils.isEmpty(json)) { + instance.domainReferer = new ArrayMap(); + } else { + instance.domainReferer = new Gson().fromJson(json, new TypeToken>() {}.getType()); + } + } + } + } + return instance; + } + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "network_referer.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance.domainReferer)); + } + public void reset() { + instance = null; + } + + private ArrayMap domainReferer; // 格式是 domain, Referer + + /** + * 用于手动下载图片 + * 有3中方法获取referer: + * 1.根据feedid,推断出referer。。优点是简单,但是可能由于rss是第三方烧制的,可能会失效。 + * 2.根据文章url,推断出referer。 + * 2.根据图片url,猜测出referer,配置繁琐、低效,但是适应性较强。(可解决图片用的是第三方服务) + * + * @param imgUrl + * @return + */ + public String guessRefererByUrl(String imgUrl) { + if (TextUtils.isEmpty(imgUrl)) { + return null; + } + + Uri uri = Uri.parse(imgUrl); + String host = uri.getHost(); + if (TextUtils.isEmpty(host)) { + return null; + } + if (domainReferer==null) { + return null; + } + + if (domainReferer.containsKey(host)) { + return StringUtils.urlEncode(domainReferer.get(host)); + } + + String[] slices = host.split("\\."); + for (int i = 1, size = slices.length; i+1 < size; i++) { + host = StringUtils.join(".", Arrays.copyOfRange(slices, i, size)); +// KLog.i("分割 Host 推测 Referer:" + host ); + if (domainReferer.containsKey(host)) { + return domainReferer.get(host); // StringUtils.urlEncode(); + } + } + return null; + } + + public void addReferer(String imgUrl, String articleUrl){ + Uri imgUri = Uri.parse(imgUrl); + String host = imgUri.getHost(); + Uri articleUri = Uri.parse(articleUrl); + domainReferer.put(host, articleUri.getScheme() + "://" + articleUri.getHost()); + save(); + } + + + // https://blog.lyz810.com/article/2016/08/referrer-policy-and-anti-leech/ + // https://www.jianshu.com/p/92bd520c0f8f + // https://www.jianshu.com/p/1be1f97167f8 + public String getRefererByPolicy2(String refererPolicy, String articleUrl){ + if(StringUtils.isEmpty(refererPolicy) || refererPolicy.equalsIgnoreCase("no-referrer") || refererPolicy.equalsIgnoreCase("undefined")){ + return null; + } + if(refererPolicy.equalsIgnoreCase("no-referrer-when-downgrade") || refererPolicy.equalsIgnoreCase("strict-origin")){ + if(!StringUtils.isEmpty(articleUrl) && articleUrl.startsWith("https://")){ + return articleUrl; + } + return null; + } + + if(refererPolicy.equalsIgnoreCase("unsafe-url")){ + return articleUrl; + } + + if(refererPolicy.equalsIgnoreCase("origin")){ + Uri uri = Uri.parse(articleUrl); + return uri.getScheme() + "://" + uri.getHost(); + } + return null; + } + + public String getRefererByPolicy(String refererPolicy, String articleUrl){ + if(StringUtils.isEmpty(refererPolicy)){ + return null; + } + if(refererPolicy.equalsIgnoreCase("no-referrer") || refererPolicy.equalsIgnoreCase("undefined")){ + return null; + } + if(refererPolicy.equalsIgnoreCase("no-referrer-when-downgrade") || refererPolicy.equalsIgnoreCase("strict-origin") || refererPolicy.equalsIgnoreCase("origin") || refererPolicy.equalsIgnoreCase("unsafe-url")){ + return articleUrl; + } + return null; + } + +} diff --git a/app/src/main/java/me/wizos/loread/config/NetworkUserAgentConfig.java b/app/src/main/java/me/wizos/loread/config/NetworkUserAgentConfig.java new file mode 100644 index 0000000..bc2f639 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/NetworkUserAgentConfig.java @@ -0,0 +1,129 @@ +package me.wizos.loread.config; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; + +import me.wizos.loread.App; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; + +public class NetworkUserAgentConfig { + private static NetworkUserAgentConfig instance; + private NetworkUserAgentConfig() {} + public static NetworkUserAgentConfig i() { + if (instance == null) { + synchronized (NetworkUserAgentConfig.class) { + if (instance == null) { + String config = FileUtil.readFile(App.i().getUserConfigPath() + "network_user_agent.json"); + instance = new NetworkUserAgentConfig(); + if (TextUtils.isEmpty(config)) { + instance.domainUserAgent = new ArrayMap(); + instance.userAgents = new ArrayMap<>(); + instance.userAgents.put("iPhone", "Mozilla/5.0 (iPhone; CPU iPhone OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1"); + instance.userAgents.put("Android", "Mozilla/5.0 (Linux; Android 5.1; MX5 Build/LMY47I) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/604.1"); + instance.userAgents.put("Chrome(PC)", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"); + } else { + instance.domainUserAgent = new Gson().fromJson(config, new TypeToken>() {}.getType()); + } + } + } + } + return instance; + } + + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "network_user_agent.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance)); + } + + private ArrayMap userAgents; // 格式是 Name, UA + + private String holdUserAgent; + private int holdUserAgentIndex = -1; + private ArrayMap domainUserAgent; // 格式是 Domain, Name + + + public String getHoldUserAgent() { + return holdUserAgent; + } + + public void setHoldUserAgent(String holdUserAgent) { + this.holdUserAgent = holdUserAgent; + } + + public ArrayMap getUserAgents() { + return userAgents; + } + + + public String guessUserAgentByUrl(String url) { + if (!TextUtils.isEmpty(holdUserAgent)) { + return userAgents.get(holdUserAgent); + } + return guessUserAgentByUrl1(url); + } + + /** + * 用于手动下载图片 + * 有3中方法获取referer: + * 1.根据feedid,推断出referer。。优点是简单,但是可能由于rss是第三方烧制的,可能会失效。 + * 2.根据文章url,推断出referer。 + * 2.根据图片url,猜测出referer,配置繁琐、低效,但是适应性较强。(可解决图片用的是第三方服务) + * + * @param url + * @return + */ + public String guessUserAgentByUrl1(String url) { + if (TextUtils.isEmpty(url)) { + return null; + } + + Uri uri = Uri.parse(url); + String host = uri.getHost(); + if (TextUtils.isEmpty(host)) { + return null; + } + if (domainUserAgent ==null) { + return null; + } + + if (domainUserAgent.containsKey(host)) { + return StringUtils.urlEncode(domainUserAgent.get(host)); + } + + String[] slices = host.split("\\."); + for (int i = 1, size = slices.length; i+1 < size; i++) { + host = StringUtils.join(".", Arrays.copyOfRange(slices, i, size)); +// KLog.i("分割 Host 推测 UA:" + host ); + if (domainUserAgent.containsKey(host)) { + return domainUserAgent.get(host); + } + } + return null; + } + + public String guessUserAgentByUrl2(String url) { + if (TextUtils.isEmpty(url)) { + return ""; + } + url = url.toLowerCase(Locale.getDefault()); + for (Map.Entry entry : domainUserAgent.entrySet()) { + if (url.contains(entry.getKey())) { + return entry.getValue(); + } + } + return ""; + } + + public void reset() { + instance = null; + } +} diff --git a/app/src/main/java/me/wizos/loread/config/SaveDirectory.java b/app/src/main/java/me/wizos/loread/config/SaveDirectory.java new file mode 100644 index 0000000..161c36f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/SaveDirectory.java @@ -0,0 +1,206 @@ +package me.wizos.loread.config; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; + +/** + * 文章保存目录: + * 1.默认目录(无分类) + * 2.订阅源 + * 3.订阅源所属的分类(分类可能有多个,如何确定) + * 4.保存时手动配置 + * + * 自定义分类 + * @author Wizos on 2020/6/14. + */ +public class SaveDirectory { + // 按 feed 或者 category 级别来设置保存目录是只能设置为 root, feed, category + // 手动修改某个 文章 的保存目录时,可以设置为 root, feed, category, 目录名称 + private String defaultDirectory; // 如果为null,代表根目录 + @SerializedName("category") + private ArrayMap settingByCategory; // root, feed, category, 目录名称 + @SerializedName("feed") + private ArrayMap settingByFeed; // root, feed, category, 目录名称 + @SerializedName("article") + private ArrayMap settingByArticle; + + private Set directories; + + + private static SaveDirectory instance; + + private SaveDirectory() { } + + public static SaveDirectory i() { + if (instance == null) { + synchronized (SaveDirectory.class) { + if (instance == null) { + Gson gson = new Gson(); + String config = FileUtil.readFile(App.i().getUserConfigPath() + "/article_save_directory.json"); + if (TextUtils.isEmpty(config)) { + instance = new SaveDirectory(); + instance.settingByFeed = new ArrayMap<>(); + instance.settingByArticle = new ArrayMap<>(); + instance.directories = new HashSet<>(); + } else { + instance = gson.fromJson(config, SaveDirectory.class); + } + } + } + } + return instance; + } + + public void reset() { + instance = null; + } + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "/article_save_directory.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance)); + } + + + public String getDirNameSettingByFeed(String feedId){ + String value; + if( settingByFeed != null && settingByFeed.containsKey(feedId) ){ + value = settingByFeed.get(feedId); + }else { + value = defaultDirectory; + } + String name; + if(StringUtils.isEmpty(value) || "loread_root".equalsIgnoreCase(value)){ + name = App.i().getString(R.string.root_directory); + }else if("loread_feed_title".equalsIgnoreCase(value)){ + name = App.i().getString(R.string.feed_title_as_directory); + }else if("loread_category_title".equalsIgnoreCase(value)){ + name = App.i().getString(R.string.category_title_as_directory); + }else { + name = value; + } + return name; + } +// public String getSavedDirectory(String feedId, String articleId) { +// if(settingByArticle != null && settingByArticle.containsKey(articleId)){ +// return settingByArticle.get(articleId); +// } +// +// if(settingByFeed != null && settingByFeed.containsKey(feedId)){ +// String dir = settingByFeed.get(feedId); +// if("loread_feed".equalsIgnoreCase(dir)){ +// Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), feedId); +// if(feed != null && !StringUtils.isEmpty(feed.getTitle())){ +// return feed.getTitle(); +// } +// } +//// else if("loread_category".equalsIgnoreCase(dir)){ +//// List categories = CoreDB.i().categoryDao().getByFeedId(App.i().getUser().getId(), feedId); +//// if(categories != null && !StringUtils.isEmpty(categories)){ +//// List titles = new ArrayList<>(categories.size()); +//// for (Category category:categories) { +//// titles.add(category.getTitle()); +//// } +//// return StringUtils.join("_",titles); +//// } +//// } +// return settingByFeed.get(feedId); +// } +// return defaultDirectory; +// } + +// String categoryId, + public String getSaveDir(String feedId, String articleId) { + // loread_root, loread_feed, loread_category, tag + String value; + if(settingByArticle != null && settingByArticle.containsKey(articleId)){ + value = settingByArticle.get(articleId); + }else if(settingByFeed != null && settingByFeed.containsKey(feedId)){ + value = settingByFeed.get(feedId); +// }else if( settingByCategory != null && settingByCategory.containsKey(categoryId) ){ +// value = settingByCategory.get(categoryId); + }else { + value = defaultDirectory; + } + + String dir = null; + if("loread_root".equalsIgnoreCase(value)){ + }else if("loread_feed_title".equalsIgnoreCase(value)){ + Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), feedId); + if(feed != null){ + dir = feed.getTitle(); + } + }else if("loread_category_title".equalsIgnoreCase(value)){ + List categories = CoreDB.i().categoryDao().getByFeedId(App.i().getUser().getId(), feedId); + if(categories != null && !StringUtils.isEmpty(categories)){ + List titles = new ArrayList<>(categories.size()); + for (Category category:categories) { + titles.add(category.getTitle()); + } + dir = StringUtils.join("_",titles); + } + }else { + dir = value; + } + return dir; + } + + public List getDirectoriesOptionValue(){ + List dirs = new ArrayList<>(); + dirs.add("loread_root"); + dirs.add("loread_feed_title"); + dirs.add("loread_category_title"); +// String[] dirs = new String[3]; +// dirs[0] = "loread_root"; +// dirs[1] = "loread_feed_title"; +// dirs[2] = "loread_category_title"; + return dirs; + } + public String[] getDirectoriesOptionName(){ +// List dirs = new ArrayList<>(); +// dirs.add(App.i().getString(R.string.default_directory)); +// dirs.add(App.i().getString(R.string.feed_title_as_directory)); +// dirs.add(App.i().getString(R.string.category_title_as_directory)); + //dirs.add(App.i().getString(R.string.custom_save_directory)); + + String[] dirs = new String[3]; + dirs[0] = App.i().getString(R.string.root_directory); + dirs[1] = App.i().getString(R.string.feed_title_as_directory); + dirs[2] = App.i().getString(R.string.category_title_as_directory); + return dirs; + } + + public void setFeedDirectory(String feedId, String directory){ + if(StringUtils.isEmpty(directory)){ + settingByFeed.remove(feedId); + }else { + settingByFeed.put(feedId,directory); + } + } + + public void setArticleDirectory(String articleId, String directory){ + if(StringUtils.isEmpty(directory)){ + settingByArticle.remove(articleId); + }else { + settingByArticle.put(articleId,directory); + } + } + + public void newDirectory(String directory){ + directories.add(directory); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/config/TestConfig.java b/app/src/main/java/me/wizos/loread/config/TestConfig.java new file mode 100644 index 0000000..d551a2b --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/TestConfig.java @@ -0,0 +1,77 @@ +package me.wizos.loread.config; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import me.wizos.loread.App; +import me.wizos.loread.utils.FileUtil; + +/** + * @author Wizos on 2018/4/14. + */ + +public class TestConfig { + private static TestConfig instance; + private TestConfig() { } + + public static TestConfig i() { + if (instance == null) { + synchronized (TestConfig.class) { + if (instance == null) { + instance = new TestConfig(); + Gson gson = new Gson(); + String config; + config = FileUtil.readFile(App.i().getUserConfigPath() + "config.json"); + if (TextUtils.isEmpty(config)) { + instance.displayRouter = new ArrayMap(); + instance.save(); + } else { + instance = gson.fromJson(config, new TypeToken() {}.getType()); + } + } + } + } + return instance; + } + + public void save() { + FileUtil.save(App.i().getUserConfigPath() + "config.json", new GsonBuilder().setPrettyPrinting().create().toJson(instance)); + } + public void reset() { + instance = null; + } + + + + private boolean ttsFile = false; + public int time = 60; + + private ArrayMap displayRouter; // 格式是 feedId, mode + + + public boolean isTtsFile() { + return ttsFile; + } + + + public String getDisplayMode(String feedId) { + if (!TextUtils.isEmpty(feedId) && displayRouter != null && displayRouter.containsKey(feedId)) { + return displayRouter.get(feedId); + } + return App.DISPLAY_RSS; + } + + public void addDisplayRouter(String feedId, String displayMode) { + displayRouter.put(feedId, displayMode); + } + + public void removeDisplayRouter(String key) { + displayRouter.remove(key); + } + + +} diff --git a/app/src/main/java/me/wizos/loread/config/Unsubscribe.java b/app/src/main/java/me/wizos/loread/config/Unsubscribe.java new file mode 100644 index 0000000..dfef0e1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/Unsubscribe.java @@ -0,0 +1,253 @@ +package me.wizos.loread.config; + + +import android.os.Environment; + +import com.socks.library.KLog; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Entities; +import org.jsoup.parser.Parser; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.bean.domain.OutFeed; +import me.wizos.loread.bean.domain.OutTag; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.User; +import me.wizos.loread.utils.FileUtil; + +/** + * Created by Wizos on 2019/5/14. + */ + +public class Unsubscribe { + public static void genBackupFile2(User user, List feeds) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + KLog.e("外置存储设备不可用"); + return; + } + if (feeds.size() == 0) { + return; + } + File docDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + if (!docDir.isDirectory()) { + return; + } + + try { + String baseUri = "loread://unsubscribe.feed"; + File file = new File(docDir.getAbsolutePath() + File.separator + user.getSource() + "_" + user.getUserName() + "_unsubscribe.opml"); + Document doc; + Element bodyNode; + Element tagNode; + Element feedNode; + + if (!file.exists()) { + Document.OutputSettings settings = new Document.OutputSettings(); + settings.syntax(Document.OutputSettings.Syntax.xml).prettyPrint(true); + doc = new Document(baseUri).outputSettings(settings); + Element opml = doc.appendElement("opml").attr("version", "1.0"); + doc.charset(Charset.defaultCharset()); + String title = "Subscriptions of " + user.getUserName() + " from " + user.getSource(); + opml.appendElement("head").appendElement("title").text(title); + bodyNode = opml.appendElement("body"); + } else { + doc = Jsoup.parse(FileUtil.readFile(file), baseUri, Parser.xmlParser()); + bodyNode = doc.selectFirst("opml > body"); + } + + List categories; + for (Feed feed : feeds) { + //categories = WithDB.i().getCategoriesByFeedId(feed.getId()); + categories = CoreDB.i().categoryDao().getByFeedId(App.i().getUser().getId(),feed.getId()); + for (Category category:categories){ + tagNode = doc.selectFirst("opml > body > outline[title=\"" + category.getTitle() + "\"]"); + if (tagNode == null) { + tagNode = bodyNode.appendElement("outline").attr("title", category.getTitle()).attr("text", category.getTitle()); + } + + feedNode = tagNode.selectFirst("outline[title=\"" + feed.getTitle() + "\"]"); + if (feedNode != null) { + continue; + } + feedNode = createSelfClosingElement("outline"); + feedNode.attr("title", feed.getTitle()) + .attr("text", feed.getTitle()) + .attr("type", "rss") + .attr("xmlUrl", feed.getFeedUrl()) + .attr("htmlUrl", feed.getHtmlUrl()); + tagNode.appendChild(feedNode); + } + } + + FileUtil.save(file, doc.outerHtml()); + } catch (IOException e) { + KLog.e("导出失败"); + } + } + + + + @Deprecated + public static void genBackupFile(User user, String baseUri, ArrayList outTags) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + KLog.e("外置存储设备不可用"); + return; + } + File docDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + if (!docDir.isDirectory()) { + return; + } + + try { + File file = new File(docDir.getAbsolutePath() + File.separator + user.getSource() + "_" + user.getUserName() + "_unsubscribe.opml"); + Document doc; + if (file.exists()) { + // doc = Jsoup.parse(file, DataUtil.getCharsetFromContentType( FileUtil.readFile(file)),baseUri); + doc = Jsoup.parse(FileUtil.readFile(file), baseUri, Parser.xmlParser()); + Element bodyNode = doc.selectFirst("opml > body"); + Element tagNode; + Element feedNode; + ArrayList outFeeds; + for (OutTag outTag : outTags) { + tagNode = doc.selectFirst("opml > body > outline[title=\"" + outTag.getTitle() + "\"]"); + if (tagNode == null) { + tagNode = bodyNode.appendElement("outline").attr("title", outTag.getTitle()).attr("text", outTag.getTitle()); + } + outFeeds = outTag.getOutFeeds(); + for (OutFeed outFeed : outFeeds) { + feedNode = tagNode.selectFirst("outline[title=\"" + outFeed.getTitle() + "\"]"); + if (feedNode != null) { + continue; + } + feedNode = createSelfClosingElement("outline"); + feedNode.attr("title", outFeed.getTitle()) + .attr("text", outFeed.getTitle()) + .attr("type", "rss") + .attr("xmlUrl", outFeed.getFeedUrl()) + .attr("htmlUrl", outFeed.getHtmlUrl()); + tagNode.appendChild(feedNode); + } + } + } else { + Document.OutputSettings settings = new Document.OutputSettings(); + settings.syntax(Document.OutputSettings.Syntax.xml).prettyPrint(true); + doc = new Document(baseUri).outputSettings(settings); + Element opml = doc.appendElement("opml").attr("version", "1.0"); + doc.charset(Charset.defaultCharset()); + String title = "Subscriptions of " + user.getUserName() + " from " + user.getSource(); + opml.appendElement("head").appendElement("title").text(title); + Element body = opml.appendElement("body"); + Element tag; + Element feed; + + ArrayList outFeeds; + //KLog.e("获取A:" + outTags.toString() ); + for (OutTag outTag : outTags) { + tag = body.appendElement("outline").attr("title", outTag.getTitle()).attr("text", outTag.getTitle()); + outFeeds = outTag.getOutFeeds(); + //KLog.e("获取B:" + outFeeds.toString() ); + for (OutFeed outFeed : outFeeds) { + feed = createSelfClosingElement("outline"); + feed.attr("title", outFeed.getTitle()) + .attr("text", outFeed.getTitle()) + .attr("type", "rss") + .attr("xmlUrl", outFeed.getFeedUrl()) + .attr("htmlUrl", outFeed.getHtmlUrl()); + tag.appendChild(feed); + } + } + } + FileUtil.save(file, doc.outerHtml()); + } catch (IOException e) { + KLog.e("导出失败"); + } + } + + private static Element createSelfClosingElement(String tagName) { + return Jsoup.parseBodyFragment("<" + tagName + "/>").body().child(0); + } + + private static Element createVoidElement(String tagName, String baseUri) { + Document document = Jsoup.parse("<" + tagName + "/>", baseUri, Parser.xmlParser()); + document.outputSettings().prettyPrint(false); + document.outputSettings().escapeMode(Entities.EscapeMode.xhtml); + return document.body() == null ? document.child(0) : document.body().child(0); + } + + + + + public static void genBackupFile3(User user, List feeds) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + KLog.e("外置存储设备不可用"); + return; + } + File docDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + if (!docDir.isDirectory()) { + return; + } + + try { + String baseUri = "loread://unsubscribe.feed"; + File file = new File(docDir.getAbsolutePath() + File.separator + user.getSource() + "_" + user.getUserName() + "_unsubscribe.opml"); + Document doc; + Element bodyNode; + Element tagNode; + Element feedNode; + + if (!file.exists()) { + Document.OutputSettings settings = new Document.OutputSettings(); + settings.syntax(Document.OutputSettings.Syntax.xml).prettyPrint(true); + doc = new Document(baseUri).outputSettings(settings); + Element opml = doc.appendElement("opml").attr("version", "1.0"); + doc.charset(Charset.defaultCharset()); + String title = "Subscriptions of " + user.getUserName() + " from " + user.getSource(); + opml.appendElement("head").appendElement("title").text(title); + bodyNode = opml.appendElement("body"); + } else { + doc = Jsoup.parse(FileUtil.readFile(file), baseUri, Parser.xmlParser()); + bodyNode = doc.selectFirst("opml > body"); + } + + List categories; + for (Feed feed : feeds) { + //categories = WithDB.i().getCategoriesByFeedId(feed.getId()); + categories = CoreDB.i().categoryDao().getByFeedId(App.i().getUser().getId(),feed.getId()); + for (Category category:categories){ + tagNode = doc.selectFirst("opml > body > outline[title=\"" + category.getTitle() + "\"]"); + if (tagNode == null) { + tagNode = bodyNode.appendElement("outline").attr("title", category.getTitle()).attr("text", category.getTitle()); + } + + feedNode = tagNode.selectFirst("outline[title=\"" + feed.getTitle() + "\"]"); + if (feedNode != null) { + continue; + } + feedNode = createSelfClosingElement("outline"); + feedNode.attr("title", feed.getTitle()) + .attr("text", feed.getTitle()) + .attr("type", "rss") + .attr("xmlUrl", feed.getFeedUrl()) + .attr("htmlUrl", feed.getHtmlUrl()); + tagNode.appendChild(feedNode); + } + } + + FileUtil.save(file, doc.outerHtml()); + } catch (IOException e) { + KLog.e("导出失败"); + } + } + +} diff --git a/app/src/main/java/me/wizos/loread/config/article_action_rule/ArticleActionRule.java b/app/src/main/java/me/wizos/loread/config/article_action_rule/ArticleActionRule.java new file mode 100644 index 0000000..4b4291c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/article_action_rule/ArticleActionRule.java @@ -0,0 +1,63 @@ +package me.wizos.loread.config.article_action_rule; + +import java.util.Set; + +public class ArticleActionRule { + private String target; // all, feed; // category: + private String attr; // title, content, author, link + //private String type; // keyword, regex; js + private String judge; // 匹配正则,未匹配正则;包含关键词,未包含关键词 + private String value; + private Set actions; + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public String getAttr() { + return attr; + } + + public void setAttr(String attr) { + this.attr = attr; + } + + public String getJudge() { + return judge; + } + + public void setJudge(String judge) { + this.judge = judge; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Set getActions() { + return actions; + } + + public void setActions(Set actions) { + this.actions = actions; + } + + @Override + public String toString() { + return "ActionRule{" + + "target='" + target + '\'' + + ", attr='" + attr + '\'' + + ", judge='" + judge + '\'' + + ", value='" + value + '\'' + + ", actions=" + actions + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/config/article_action_rule/Condition.java b/app/src/main/java/me/wizos/loread/config/article_action_rule/Condition.java new file mode 100644 index 0000000..2b53dad --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/article_action_rule/Condition.java @@ -0,0 +1,41 @@ +//package me.wizos.loread.config.article_action_rule; +// +//public class Condition { +// private String target; +// private String attr; +// //private String type; // keyword, regex; js +// private String judge; // 匹配正则,未匹配正则;包含关键词,未包含关键词 +// private String value; +// +// public String getTarget() { +// return target; +// } +// +// public void setTarget(String target) { +// this.target = target; +// } +// +// public String getAttr() { +// return attr; +// } +// +// public void setAttr(String attr) { +// this.attr = attr; +// } +// +// public String getJudge() { +// return judge; +// } +// +// public void setJudge(String judge) { +// this.judge = judge; +// } +// +// public String getValue() { +// return value; +// } +// +// public void setValue(String value) { +// this.value = value; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/config/article_extract_rule/ArticleExtractRule.java b/app/src/main/java/me/wizos/loread/config/article_extract_rule/ArticleExtractRule.java new file mode 100644 index 0000000..abaf94b --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/article_extract_rule/ArticleExtractRule.java @@ -0,0 +1,43 @@ +package me.wizos.loread.config.article_extract_rule; + +import com.google.gson.annotations.SerializedName; + +public class ArticleExtractRule { + private Selector selector = Selector.css; + + @SerializedName("document_trim") + private String documentTrim; + + private String content; + + @SerializedName("content_strip") + private String contentStrip; + + @SerializedName("content_trim") + private String contentTrim; + + public Selector getSelector() { + return selector; + } + + public String getDocumentTrim() { + return documentTrim; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getContentStrip() { + return contentStrip; + } + + public String getContentTrim() { + return contentTrim; + } + +} diff --git a/app/src/main/java/me/wizos/loread/config/article_extract_rule/Selector.java b/app/src/main/java/me/wizos/loread/config/article_extract_rule/Selector.java new file mode 100644 index 0000000..0ede0b7 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/config/article_extract_rule/Selector.java @@ -0,0 +1,17 @@ +package me.wizos.loread.config.article_extract_rule; + +public enum Selector { + css("css"),xpath("xpath"),regex("regex"); + + private String selector; + + Selector(String selector) { } + + public String getSelector() { + return selector; + } + + public void setSelector(String selector) { + this.selector = selector; + } +} diff --git a/app/src/main/java/me/wizos/loread/data/WithDB.java b/app/src/main/java/me/wizos/loread/data/WithDB.java deleted file mode 100644 index bc13046..0000000 --- a/app/src/main/java/me/wizos/loread/data/WithDB.java +++ /dev/null @@ -1,828 +0,0 @@ -package me.wizos.loread.data; - -import android.database.Cursor; - -import com.socks.library.KLog; - -import org.greenrobot.greendao.query.QueryBuilder; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import me.wizos.loread.App; -import me.wizos.loread.db.Article; -import me.wizos.loread.db.Feed; -import me.wizos.loread.db.Tag; -import me.wizos.loread.db.dao.ArticleDao; -import me.wizos.loread.db.dao.DaoSession; -import me.wizos.loread.db.dao.FeedDao; -import me.wizos.loread.db.dao.TagDao; -import me.wizos.loread.net.Api; - -/** - * @author Wizos on 2016/3/12. - */ -public class WithDB { - private static WithDB withDB; - public TagDao tagDao; - public FeedDao feedDao; - public ArticleDao articleDao; - - private WithDB() {} - - - public static WithDB i() { - if (withDB == null) { // 双重锁定,只有在 withDB 还没被初始化的时候才会进入到下一行,然后加上同步锁 - synchronized (WithDB.class) { // 同步锁,避免多线程时可能 new 出两个实例的情况 - if (withDB == null) { - withDB = new WithDB(); - withDB.tagDao = App.i().getDaoSession().getTagDao(); - withDB.feedDao = App.i().getDaoSession().getFeedDao(); - withDB.articleDao = App.i().getDaoSession().getArticleDao(); - } - } - } - return withDB; - } - - public DaoSession getDaoSession() { - return App.i().getDaoSession(); - } - - - // 异步查询 -// public void query(final String streamId, final DBInterface.Query dbCallBack ){ -// AsyncSession asyncSession = App.i().getDaoSession().startAsyncSession(); -// asyncSession.setListenerMainThread(new AsyncOperationListener() { -// @Override -// public void onAsyncOperationCompleted(AsyncOperation operation) { -// dbCallBack.onQuerySuccess( streamId, ((List) operation.getResult()).size()); -// } -// }); -// Query
query = articleDao.queryBuilder().where(ArticleDao.Properties.ReadState.notEq(Api.ART_READED), ArticleDao.Properties.OriginStreamId.eq(streamId)).build(); -// asyncSession.queryList(query); -// } - - - - public void delTag(Tag tag) { - if (tag == null) { - return; - } - tagDao.delete(tag); - } - - public void insertTag(Tag tag) { - if (tag == null) { - return; - } - tagDao.insertOrReplace(tag); - } - - // 自己写的 - public void saveTag(Tag tag) { - if (tag == null) { - return; - } - if (tagDao.queryBuilder().where(TagDao.Properties.Id.eq(tag.getId())).listLazy().size() == 0) { - tagDao.insertOrReplace(tag); - } else { // already exist - tagDao.update(tag); - } - } - // 自己写的 - public void coverSaveTags(List tags) { - tagDao.deleteAll(); - tagDao.insertOrReplaceInTx(tags); - } - -// private List getAllTags(List tags) { -// Tag rootTag = new Tag(); -// Tag noLabelTag = new Tag(); -// long userID = WithPref.i().getUseId(); -// rootTag.setTitle("所有文章"); -// noLabelTag.setTitle("未分类"); -// -// rootTag.setId("\"user/" + userID + Api.U_READING_LIST + "\""); -// rootTag.setSortid("00000000"); -// rootTag.__setDaoSession(App.i().getDaoSession()); -// -// noLabelTag.setId("\"user/" + userID + Api.U_NO_LABEL + "\""); -// noLabelTag.setSortid("00000001"); -// noLabelTag.__setDaoSession(App.i().getDaoSession()); -// -// tags.add(0, noLabelTag); -// tags.add(0, rootTag); -// -// KLog.d("【listTag】 " + rootTag.toString()); -// return tags; -// } - - - - - public List getTags() { - List tagList = tagDao.loadAll(); - Collections.sort(tagList, new Comparator() { - @Override - public int compare(Tag o1, Tag o2) { - return Integer.valueOf(o1.getSortid()).compareTo(Integer.valueOf(o2.getSortid())); // 待优化,可以直接把 Sortid字段改为int型 - } - }); - return tagList; - } -// public List getTags() { -// QueryBuilder tags = tagDao.queryBuilder().orderAsc(TagDao.Properties.Sortid); -// return tags.listLazy(); -// } - - public Tag getTag(String tagId) { - return tagDao.queryBuilder().where(TagDao.Properties.Id.eq(tagId)).unique(); - } - - - - public List getFeeds() { - return feedDao.loadAll(); - } - - public Feed getFeed(String id) { - return feedDao.queryBuilder().where(FeedDao.Properties.Id.eq(id)).unique(); - } - - public List getFeeds(List ids) { - return feedDao.queryBuilder().where(FeedDao.Properties.Id.in(ids)).build().listLazy(); - } - - public void delTags(List tags) { - if (tags == null) { - return; - } - tagDao.deleteInTx(tags); - } - - public void delFeeds(List feeds) { - if (feeds == null) { - return; - } - feedDao.deleteInTx(feeds); - } - public void delFeed(String feedId) { - if (feedId == null) { - return; - } - feedDao.deleteByKeyInTx(feedId); - } - public void delFeed(Feed feed) { - if (feed == null) { - return; - } - feedDao.deleteInTx(feed); - } - - public void unsubscribeFeed(Feed feed) { - if (feed == null) { - return; - } - List
articles = getArtsInFeedToDel(feed.getId()); - articleDao.deleteInTx(articles); - feedDao.deleteInTx(feed); - } - - public void addFeed(Feed feed) { - if (feed != null) { - feedDao.insertInTx(feed); - } - } - - public void saveFeed(Feed feed) { - feedDao.update(feed); - } - - public void saveFeeds(List feeds) { - feedDao.insertOrReplaceInTx(feeds); - } - public void coverSaveFeeds(List feeds) { - feedDao.deleteAll(); - feedDao.insertOrReplaceInTx(feeds); - } - - public void coverSaveFeeds2(List feeds) { - feedDao.updateInTx(feeds); - } - - public void updateArtsFeedTitle(Feed feed) { - if (feed == null) { - return; - } - QueryBuilder q = articleDao.queryBuilder() - .where(ArticleDao.Properties.OriginStreamId.eq(feed.getId())); - List
articles = q.list(); - for (Article article : articles) { - article.setOriginTitle(feed.getTitle()); - } - articleDao.updateInTx(articles); - } - public void updateFeed(Feed feed) { - if (feed != null) { - feedDao.update(feed); - } - } - - public void updateFeedsCategoryId(String sourceTagId, String destTagId) { - QueryBuilder q = feedDao.queryBuilder() - .where(FeedDao.Properties.Categoryid.eq(sourceTagId)); - List feeds = q.list(); - for (Feed feed : feeds) { - feed.setCategoryid(destTagId); - } - feedDao.updateInTx(feeds); - -// String queryString = "SELECT " + ArticleDao.TABLENAME + ".* FROM " -// + ArticleDao.TABLENAME + " LEFT JOIN " + FeedDao.TABLENAME + " ON " + ArticleDao.TABLENAME + "." + ArticleDao.Properties.OriginStreamId.columnName + " = " + FeedDao.TABLENAME + "." + FeedDao.Properties.Id.columnName -// + " WHERE " -// + ArticleDao.TABLENAME + "." + ArticleDao.Properties.StarState.columnName + " = \"" + Api.ART_STARED + "\" AND " -// + "(" + FeedDao.TABLENAME + "." + FeedDao.Properties.Id.columnName + " IS NULL OR " + FeedDao.TABLENAME + "." + FeedDao.Properties.Categoryid.columnName + " IS NULL )" -// + "ORDER BY " + ArticleDao.Properties.TimestampUsec.columnName + " DESC"; - } - - - public void saveArticle(Article article) { - if (article.getId() != null) { - articleDao.insertOrReplace(article); -// articleDao.update(article); - } - } - - public void updateArticle(Article article) { - if (article.getId() != null) { - articleDao.update(article); - } - } - - public void saveArticles(List
articleList) { - if (articleList != null && articleList.size() != 0) { // new fetch - articleDao.insertOrReplaceInTx(articleList); - } - } - - - public Article getArticle(String articleId) { - KLog.e("要获取的文章是:" + articleId); - return articleDao.queryBuilder().where(ArticleDao.Properties.Id.eq(articleId)).unique(); - } - - - public void setReaded(Article article) { - if (article == null) { - return; - } -// KLog.e("未读数A:" + getFeed(article.getOriginStreamId()).getUnreadCount() + " " + article.getReadStatus()); - article.setReadStatus(Api.READED); - updateArticle(article); - -// KLog.e("未读数B:" + getFeed(article.getOriginStreamId()).getUnreadCount()); -// KLog.e("未读数C:" + getCount(article.getOriginStreamId())); - Feed feed = getFeed(article.getOriginStreamId()); - if (feed == null) { - return; - } - feed.setUnreadCount(feed.getUnreadCount() - 1); - saveFeed(feed); - -// Tag tag = getTag(feed.getCategoryid()); -// if (tag == null) { -// return; -// } -// tag.setUnreadCount(tag.getUnreadCount() - 1); -// saveTag(tag); - } - - public void setUnread(Article article) { - if (article == null) { - return; - } - - article.setReadStatus(Api.UNREAD); - updateArticle(article); - - Feed feed = getFeed(article.getOriginStreamId()); - if (feed == null) { - return; - } - feed.setUnreadCount(feed.getUnreadCount() + 1); - saveFeed(feed); - -// Tag tag = getTag(feed.getCategoryid()); -// if (tag == null) { -// return; -// } -// tag.setUnreadCount(tag.getUnreadCount() + 1); -// saveTag(tag); - } - - - public void setUnreading(Article article) { - if (article == null) { - return; - } - int offest = 1; - if (article.getReadStatus() == Api.UNREAD || article.getReadStatus() == Api.UNREADING) { - offest = 0; - } - - article.setReadStatus(Api.UNREADING); - updateArticle(article); - - Feed feed = getFeed(article.getOriginStreamId()); - if (feed == null) { - return; - } - - feed.setUnreadCount(feed.getUnreadCount() + offest); - saveFeed(feed); - - -// Tag tag = getTag(feed.getCategoryid()); -// if (tag == null) { -// return; -// } -// KLog.e("未读数的数值为:" + tag.getUnreadCount() + " " + offest); -// tag.setUnreadCount(tag.getUnreadCount() + offest); -// saveTag(tag); - } - - - /** - * 【升序】Collections.sort(list,Collator.i(java.util.Locale.CHINA));//注意:是根据的汉字的拼音的字母排序的,而不是根据汉字一般的排序方法 - * 【降序】Collections.reverse(list);//不指定排序规则时,也是按照字母的来排序的 - **/ - public void delArt(List
articles) { - if (articles.size() != 0) { // new fetch - articleDao.deleteInTx( articles ); - } - } - - - /** - * 获取状态为已阅读,未加星,小于X时间的文章,用于清理文章 - * - * @param time 爬取时间戳 - * @return 文章列表 - */ - public List
getArtInReadedUnstarLtTime(long time) { - QueryBuilder
q = articleDao.queryBuilder(); - q.where(q.and(ArticleDao.Properties.ReadStatus.eq(Api.READED), ArticleDao.Properties.StarStatus.eq(Api.UNSTAR), ArticleDao.Properties.CrawlTimeMsec.lt(time))); - return q.listLazy(); - } - - - public List
getArtInReadedBox(long time) { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.ReadStatus.eq(Api.READED), ArticleDao.Properties.SaveDir.eq(Api.SAVE_DIR_BOX), ArticleDao.Properties.CrawlTimeMsec.lt(time)); - return q.listLazy(); - } - - - public List
getArtInReadedStore(long time) { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.ReadStatus.eq(Api.READED), ArticleDao.Properties.SaveDir.eq(Api.SAVE_DIR_STORE), ArticleDao.Properties.CrawlTimeMsec.lt(time)); - return q.listLazy(); - } - - /* - * 文章列表页会有12种组合:某个 Categories 内的 UnRead[含UnReading], Stared, All。某个 OriginStreamId 内的 UnRead[含UnReading], Stared, All。 - * 所有定下来去获取文章的函数也有6个:getArtsUnreadInTag(), getArtsStaredInTag(), getArtsAllInTag(),getUnreadArtsInFeed(), getStaredArtsInFeed(), getAllArtsInFeed() - */ - public List
getSearchedArts(String keyword) { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.Title.like("%" + keyword + "%")) // , ArticleDao.Properties.Summary.like("%" + keyword + "%") - .orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - /** - * 获取所有文章 - */ - public List
getArtsAllNoOrder() { // 速度比要排序的全文更快 - return articleDao.loadAll(); - } - - - public List
getArtsAll() { - long time = System.currentTimeMillis() - WithPref.i().getClearBeforeDay() * 24 * 3600 * 1000L - 300 * 1000L; - QueryBuilder
q = articleDao.queryBuilder(); - q.whereOr(ArticleDao.Properties.ReadStatus.notEq(Api.READED), ArticleDao.Properties.StarStatus.eq(Api.STARED), q.and(ArticleDao.Properties.ReadStatus.eq(Api.READED), ArticleDao.Properties.CrawlTimeMsec.gt(time))); - q.orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - public List
getArtsUnreading() { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.ReadStatus.eq(Api.UNREADING)); - return q.listLazy(); - } - - /** - * 获取所有加星的文章 - */ - public List
getArtsStared() { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.StarStatus.eq(Api.STARED)) - .orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - public List
getArtsUnread() { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.ReadStatus.notEq(Api.READED)) - .orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - public List
getArtsUnreadNoOrder() { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.ReadStatus.notEq(Api.READED)); - return q.listLazy(); - } - - - public List
getArtsUnreadInTag(Tag tag) { - QueryBuilder
q = articleDao.queryBuilder(); - q.join(ArticleDao.Properties.OriginStreamId, Feed.class).where(FeedDao.Properties.Categoryid.eq(tag.getId())); - q.where(ArticleDao.Properties.ReadStatus.notEq(Api.READED)); - q.orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - public List
getArtsStaredInTag(Tag tag) { - KLog.e("getArtsStaredInTag2", Api.STARED + tag.getId()); - QueryBuilder
q = articleDao.queryBuilder(); - q.join(ArticleDao.Properties.OriginStreamId, Feed.class).where(FeedDao.Properties.Categoryid.eq(tag.getId())); - q.where(ArticleDao.Properties.StarStatus.eq(Api.STARED)); - q.orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - - public List
getArtsAllInTag(Tag tag) { - // 最后的 300 * 1000L 是留前5分钟时间的不删除 WithPref.i().getClearBeforeDay() - long time = System.currentTimeMillis() - WithPref.i().getClearBeforeDay() * 24 * 3600 * 1000L - 300 * 1000L; - QueryBuilder
q = articleDao.queryBuilder(); - q.join(ArticleDao.Properties.OriginStreamId, Feed.class).where(FeedDao.Properties.Categoryid.eq(tag.getId())); - q.whereOr(ArticleDao.Properties.ReadStatus.notEq(Api.READED), ArticleDao.Properties.StarStatus.eq(Api.STARED), q.and(ArticleDao.Properties.ReadStatus.eq(Api.READED), ArticleDao.Properties.CrawlTimeMsec.gt(time))); - q.orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - - return q.listLazy(); - } - - - public List
getArtsAllInFeed(String streamId) { - // 最后的 300 * 1000L 是留前5分钟时间的不删除 WithPref.i().getClearBeforeDay() - long time = System.currentTimeMillis() - WithPref.i().getClearBeforeDay() * 24 * 3600 * 1000L - 300 * 1000L; - QueryBuilder
q = articleDao.queryBuilder(); - q.where(ArticleDao.Properties.OriginStreamId.eq(streamId)); - q.whereOr(ArticleDao.Properties.ReadStatus.notEq(Api.READED), ArticleDao.Properties.StarStatus.eq(Api.STARED), q.and(ArticleDao.Properties.ReadStatus.eq(Api.READED), ArticleDao.Properties.CrawlTimeMsec.gt(time))); - q.orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - public List
getArtsInFeedToDel(String streamId) { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.StarStatus.eq(Api.UNSTAR), ArticleDao.Properties.OriginStreamId.eq(streamId)); - return q.listLazy(); - } - - public List
getArtsUnreadInFeed(String streamId) { - String queryString = - "SELECT COUNT(1) AS UNREADCOUNT" + - " FROM ARTICLE WHERE READ_STATUS != " + Api.READED + " AND ORIGIN_STREAM_ID = '" + streamId + "'"; - Cursor cursor = getDaoSession().getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return null; - } - KLog.e("获取A:" + queryString); - cursor.moveToNext(); - KLog.e("获取B:" + cursor.getInt(0)); - - - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.ReadStatus.notEq(Api.READED), ArticleDao.Properties.OriginStreamId.eq(streamId)) - .orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - KLog.e("获取C:" + q.listLazy().size()); - return q.listLazy(); - } - - public List
getArtsStaredInFeed(String streamId) { - QueryBuilder
q = articleDao.queryBuilder() - .where(ArticleDao.Properties.StarStatus.eq(Api.STARED), ArticleDao.Properties.OriginStreamId.eq(streamId)) - .orderDesc(ArticleDao.Properties.Updated, ArticleDao.Properties.Published); - return q.listLazy(); - } - - public ArrayList getFeedsWithUnreadCount() { - String queryString = - "SELECT ID,TITLE,CATEGORYID,CATEGORYLABEL,SORTID,FIRSTITEMMSEC,URL,HTMLURL,ICONURL,OPEN_MODE,UNREADCOUNT,NEWEST_ITEM_TIMESTAMP_USEC" + - " FROM FEED_UNREAD_COUNT"; - Cursor cursor = getDaoSession().getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return null; - } - - ArrayList feeds = new ArrayList<>(); - Feed feed; - while (cursor.moveToNext()) { - feed = new Feed(); - feed.setId(cursor.getString(0)); - feed.setTitle(cursor.getString(1)); - feed.setCategoryid(cursor.getString(2)); - feed.setCategorylabel(cursor.getString(3)); - feed.setSortid(cursor.getString(4)); - feed.setFirstitemmsec(cursor.getLong(5)); - feed.setUrl(cursor.getString(6)); - feed.setHtmlurl(cursor.getString(7)); - feed.setIconurl(cursor.getString(8)); - feed.setOpenMode(cursor.getString(9)); - feed.setUnreadCount(cursor.getInt(10)); - feed.setNewestItemTimestampUsec(cursor.getLong(11)); - feeds.add(feed); - } - cursor.close(); - return feeds; - } - - - public ArrayList getFeedsWithStaredCount() { - String queryString = - "SELECT ID,TITLE,CATEGORYID,CATEGORYLABEL,SORTID,FIRSTITEMMSEC,URL,HTMLURL,ICONURL,OPEN_MODE,NEWEST_ITEM_TIMESTAMP_USEC,COUNT" + - " FROM FEED" + - " LEFT JOIN (SELECT COUNT(1) AS COUNT, ORIGIN_STREAM_ID" + - " FROM ARTICLE WHERE STAR_STATUS == " + Api.STARED + " GROUP BY ORIGIN_STREAM_ID)" + - " ON ID = ORIGIN_STREAM_ID"; - Cursor cursor = getDaoSession().getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return null; - } - - ArrayList feeds = new ArrayList<>(); - Feed feed; - while (cursor.moveToNext()) { - feed = new Feed(); - feed.setId(cursor.getString(0)); - feed.setTitle(cursor.getString(1)); - feed.setCategoryid(cursor.getString(2)); - feed.setCategorylabel(cursor.getString(3)); - feed.setSortid(cursor.getString(4)); - feed.setFirstitemmsec(cursor.getLong(5)); - feed.setUrl(cursor.getString(6)); - feed.setHtmlurl(cursor.getString(7)); - feed.setIconurl(cursor.getString(8)); - feed.setOpenMode(cursor.getString(9)); - feed.setNewestItemTimestampUsec(cursor.getLong(10)); - feed.setUnreadCount(cursor.getInt(11)); - feeds.add(feed); - } - cursor.close(); - return feeds; - } - - - public ArrayList getTagsWithUnreadCount() { - String queryString = "SELECT ID,TITLE,SORTID,NEWEST_ITEM_TIMESTAMP_USEC,UNREADCOUNT FROM TAG_UNREAD_COUNT"; -// KLog.e("测getArtsAllNoTag2:" + queryString); - Cursor cursor = getDaoSession().getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return null; - } - ArrayList tags = new ArrayList<>(cursor.getCount()); - Tag tag; - while (cursor.moveToNext()) { - tag = new Tag(); - tag.setId(cursor.getString(0)); - tag.setTitle(cursor.getString(1)); - tag.setSortid(cursor.getString(2)); - tag.setNewestItemTimestampUsec(cursor.getLong(3)); - tag.setUnreadCount(cursor.getInt(4)); - tag.__setDaoSession(App.i().getDaoSession()); - tags.add(tag); - KLog.e("标题:" + tag.getTitle() + " , " + tag.getUnreadCount()); - } - cursor.close(); - return tags; - } - - - public ArrayList getTagsWithStaredCount() { - String queryString = "SELECT ID,TITLE,SORTID,NEWEST_ITEM_TIMESTAMP_USEC,COUNT FROM TAG" + - " LEFT JOIN (SELECT CATEGORYID,SUM(UNREAD_COUNT) AS COUNT FROM FEED GROUP BY CATEGORYID ) ON ID = CATEGORYID"; - Cursor cursor = getDaoSession().getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return null; - } - ArrayList tags = new ArrayList<>(cursor.getCount()); - Tag tag; - while (cursor.moveToNext()) { - tag = new Tag(); - tag.setId(cursor.getString(0)); - tag.setTitle(cursor.getString(1)); - tag.setSortid(cursor.getString(2)); - tag.setNewestItemTimestampUsec(cursor.getLong(3)); - tag.setUnreadCount(cursor.getInt(4)); - tag.__setDaoSession(App.i().getDaoSession()); - tags.add(tag); - KLog.e("标题:" + tag.getTitle() + " , " + tag.getUnreadCount()); - } - cursor.close(); - return tags; - } - - - - public int getUnreadArtsCount() { - QueryBuilder
q = articleDao.queryBuilder(); - q.where(ArticleDao.Properties.ReadStatus.notEq(Api.READED)); - return (int) q.buildCount().count(); - } - - public int getUnreadArtsCountNoTag() { - String queryString = "SELECT count(*) FROM" - + " ARTICLE LEFT JOIN FEED ON ARTICLE.ORIGIN_STREAM_ID = FEED.ID" - + " WHERE ARTICLE.READ_STATUS != " + Api.READED + " AND" - + " FEED.CATEGORYID = \"user/" + WithPref.i().getUseId() + Api.U_NO_LABEL + "\""; -// KLog.e("测getUnreadArtsCountNoTag:" + queryString); - Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return 0; - } - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return count; - } - -// public int getStaredArtsCountNoTag() { -// // SELECT count(*) FROM Article LEFT JOIN Feed ON Article.ORIGIN_STREAM_ID = Feed.Id WHERE Article.Star_State like "Stared" AND ( Feed.Categoryid IS "user/1006097346/state/com.google/no-label" OR Feed.Categoryid IS NULL ); -// String queryString = "SELECT count(*) FROM " -// + ArticleDao.TABLENAME + " LEFT JOIN " + FeedDao.TABLENAME + " ON " + ArticleDao.TABLENAME + "." + ArticleDao.Properties.OriginStreamId.columnName + " = " + FeedDao.TABLENAME + "." + FeedDao.Properties.Id.columnName -// + " WHERE " -// + ArticleDao.TABLENAME + "." + ArticleDao.Properties.StarState.columnName + " = \"" + Api.ART_STARED + "\" AND " -// + "(" + FeedDao.TABLENAME + "." + FeedDao.Properties.Id.columnName + " IS NULL OR " + FeedDao.TABLENAME + "." + FeedDao.Properties.Categoryid.columnName + " IS \"user/" + WithPref.i().getUseId() + Api.U_NO_LABEL + "\" )"; -//// KLog.e("测getStaredArtsCountNoTag:" + queryString); -// Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); -// if (cursor == null) { -// return 0; -// } -// cursor.moveToFirst(); -// int count = cursor.getInt(0); -// cursor.close(); -// return count; -// } -// -// public int getAllArtsCountNoTag() { -// // 在未分类的文章有以下几种: -// // 1.有订阅源,但是订阅源没有tag(未读的时候,还有一个条件是文章的状态为unread) -// // 2.没有订阅源,但是是加星的(加星的时候) -// // (全部的时候是他们的合集) -// // 查询在一张表不在另外一张表的记录:http://blog.csdn.net/c840898727/article/details/42238363 -// // select ta.* from ta left join tb on ta.id=tb.id where tb.id is null -// String queryString = "SELECT count(*) FROM " -// + ArticleDao.TABLENAME + " LEFT JOIN " + FeedDao.TABLENAME + " ON " + ArticleDao.TABLENAME + "." + ArticleDao.Properties.OriginStreamId.columnName + " = " + FeedDao.TABLENAME + "." + FeedDao.Properties.Id.columnName -// + " WHERE " -// + FeedDao.TABLENAME + "." + FeedDao.Properties.Id.columnName + " IS NULL OR " + FeedDao.TABLENAME + "." + FeedDao.Properties.Categoryid.columnName + " IS \"user/" + WithPref.i().getUseId() + Api.U_NO_LABEL + "\""; -//// KLog.e("测getAllArtsCountNoTag:" + queryString); -// Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); -// if (cursor == null) { -// return 0; -// } -// cursor.moveToFirst(); -// int count = cursor.getInt(0); -// cursor.close(); -// return count; -// } - - /** - * 获取文章是否有重复 - * - * @return - */ - public List
getDuplicateArticle() { - String queryString = "select * from ARTICLE group by CANONICAL,TITLE having count(*) > 1"; - Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return new ArrayList<>(); - } - List
articles = new ArrayList<>(cursor.getCount()); - while (cursor.moveToNext()) { - articles.add(genArticle(new Article(), cursor)); - } - cursor.close(); - return articles; - } - - public List
getDuplicateArticle(String title, String href) { - return articleDao.queryBuilder(). - where(ArticleDao.Properties.Title.eq(title), ArticleDao.Properties.Canonical.eq(href)) - .orderAsc(ArticleDao.Properties.Updated).list(); - - } - - - public List
getArtsUnreadNoTag() { - String queryString = "SELECT ARTICLE.* FROM " - + "ARTICLE LEFT JOIN FEED ON ARTICLE.ORIGIN_STREAM_ID = FEED.ID" - + " WHERE ARTICLE.READ_STATUS != " + Api.READED + " AND" - + " FEED.CATEGORYID = \"user/" + WithPref.i().getUseId() + Api.U_NO_LABEL + "\"" - + " ORDER BY UPDATED,PUBLISHED DESC"; -// KLog.e("getArtsUnreadNoTag:" + queryString); - Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return new ArrayList<>(); - } - List
articles = new ArrayList<>(cursor.getCount()); - while (cursor.moveToNext()) { - articles.add(genArticle(new Article(), cursor)); - } - cursor.close(); - return articles; - } - - - // 为分类的加星文章包含两部分:1.是在订阅中,但是没有分类的加星文章;2.是不在订阅中,加星的文章 - public List
getArtsStaredNoTag() { - // select ta.* from ta left join tb on ta.id=tb.id where tb.id is null - String queryString = "SELECT ARTICLE.* FROM " - + "ARTICLE LEFT JOIN FEED ON ARTICLE.ORIGIN_STREAM_ID = FEED.ID" - + " WHERE ARTICLE.STAR_STATUS = " + Api.STARED + " AND " - + " (FEED.ID IS NULL OR FEED.CATEGORYID = \"user/" + WithPref.i().getUseId() + Api.U_NO_LABEL + "\" )" - + " ORDER BY UPDATED,PUBLISHED DESC"; -// KLog.e("测getArtsStaredNoTag2:" + queryString); - Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return new ArrayList<>(); - } - List
articles = new ArrayList<>(cursor.getCount()); - while (cursor.moveToNext()) { - articles.add(genArticle(new Article(), cursor)); - } - cursor.close(); - return articles; - } - - // 未分类的所有文章包含两部分:1.是在订阅中,但是没有分类的文章;2.是不在订阅中,加星的文章 - public List
getArtsAllNoTag() { - String queryString = "SELECT ARTICLE.* FROM " - + "ARTICLE LEFT JOIN FEED ON ARTICLE.ORIGIN_STREAM_ID = FEED.ID " - + "WHERE FEED.ID IS NULL OR FEED.CATEGORYID = \"user/" + WithPref.i().getUseId() + Api.U_NO_LABEL + "\" " - + "ORDER BY UPDATED,PUBLISHED DESC"; -// KLog.e("测getArtsAllNoTag2:" + queryString); - Cursor cursor = articleDao.getDatabase().rawQuery(queryString, new String[]{}); - if (cursor == null) { - return new ArrayList<>(); - } - List
articles = new ArrayList<>(cursor.getCount()); - while (cursor.moveToNext()) { - articles.add(genArticle(new Article(), cursor)); - } - cursor.close(); - return articles; - } - - - - - private Article genArticle(Article article, Cursor cursor) { - article.setId(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Id.columnName))); - article.setCrawlTimeMsec(cursor.getLong(cursor.getColumnIndex(ArticleDao.Properties.CrawlTimeMsec.columnName))); - article.setTimestampUsec(cursor.getLong(cursor.getColumnIndex(ArticleDao.Properties.TimestampUsec.columnName))); - article.setCategories(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Categories.columnName))); - article.setTitle(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Title.columnName))); - article.setPublished(cursor.getLong(cursor.getColumnIndex(ArticleDao.Properties.Published.columnName))); - article.setUpdated(cursor.getLong(cursor.getColumnIndex(ArticleDao.Properties.Updated.columnName))); - article.setStarred(cursor.getLong(cursor.getColumnIndex(ArticleDao.Properties.Starred.columnName))); - article.setEnclosure(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Enclosure.columnName))); - article.setCanonical(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Canonical.columnName))); - article.setAlternate(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Alternate.columnName))); - article.setSummary(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Summary.columnName))); - article.setContent(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Content.columnName))); - article.setAuthor(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.Author.columnName))); - article.setReadState(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.ReadState.columnName))); - article.setStarState(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.StarState.columnName))); - article.setReadStatus(cursor.getInt(cursor.getColumnIndex(ArticleDao.Properties.ReadStatus.columnName))); - article.setStarStatus(cursor.getInt(cursor.getColumnIndex(ArticleDao.Properties.StarStatus.columnName))); - article.setSaveDir(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.SaveDir.columnName))); - article.setImgState(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.ImgState.columnName))); - article.setCoverSrc(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.CoverSrc.columnName))); - article.setOriginStreamId(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.OriginStreamId.columnName))); - article.setOriginTitle(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.OriginTitle.columnName))); - article.setOriginHtmlUrl(cursor.getString(cursor.getColumnIndex(ArticleDao.Properties.OriginHtmlUrl.columnName))); - return article; - } - - public void clear() { - tagDao.deleteAll(); - feedDao.deleteAll(); - articleDao.deleteAll(); - } -} diff --git a/app/src/main/java/me/wizos/loread/data/WithPref.java b/app/src/main/java/me/wizos/loread/data/WithPref.java deleted file mode 100644 index 62e6f35..0000000 --- a/app/src/main/java/me/wizos/loread/data/WithPref.java +++ /dev/null @@ -1,327 +0,0 @@ -package me.wizos.loread.data; - -import android.app.Activity; -import android.content.SharedPreferences; - -import me.wizos.loread.App; - -/** - * @author Wizos - * @date 2016/4/30 - * 内部设置 - */ -public class WithPref { - private static final String TAG = "WithPref"; - private static WithPref withPref; - private static SharedPreferences mySharedPreferences; - private static SharedPreferences.Editor editor; - - // TODO: 2018/7/14 修整代码,将setXXX,getXXX,统一改为setOOO(XXX,defValue) 的形式 -// public static final String REFRESH_INTERVAL = "refresh.interval"; -// public static final String SIXTY_MINUTES = "3600000"; - - - private WithPref() { - } - - public static WithPref i() { - // 双重锁定,只有在 mySharedPreferences 还没被初始化的时候才会进入到下一行,然后加上同步锁 - if (withPref == null) { - // 同步锁,避免多线程时可能 new 出两个实例的情况 - synchronized (WithPref.class) { - if (withPref == null) { - withPref = new WithPref(); - mySharedPreferences = App.i().getSharedPreferences("loread", Activity.MODE_PRIVATE); - editor = mySharedPreferences.edit(); - } - } - } - return withPref; - } - - public void clear() { - editor.clear(); - editor.apply(); - } - - private String read(String key, String defaultValue) { - return mySharedPreferences.getString(key, defaultValue);//getString()第二个参数为缺省值,如果preference中不存在该key,将返回缺省值 - } - - private void save(String key, String value) { -// SharedPreferences.Editor editor = mySharedPreferences.edit();//实例化SharedPreferences.Editor对象 - editor.putString(key, value); //用putString的方法保存数据 - editor.apply(); //提交当前数据 - } - - private boolean read(String key, boolean defaultValue) { - return mySharedPreferences.getBoolean(key, defaultValue); - } - - private void save(String key, boolean value) { - //用putString的方法保存数据 - editor.putBoolean(key, value); - editor.apply(); //提交当前数据 - } - - private int read(String key, int value) { - return mySharedPreferences.getInt(key, value); - } - - private void save(String key, int value) { - editor.putInt(key, value); - editor.apply(); - } - - private long read(String key, long value) { - return mySharedPreferences.getLong(key, value); - } - - private void save(String key, long value) { - editor.putLong(key, value); - editor.apply(); - } - - - /* 账号 */ - public String getAuth() { - return read("Auth", ""); - } - public void setAuth(String auth) { - save("Auth", auth); - } - - public String getAccountID() { - return read("AccountID", ""); - } - public void setAccountID(String accountID) { - save("AccountID", accountID); - } - - public String getAccountPD() { - return read("AccountPD", ""); - } - public void setAccountPD(String accountPD) { - save("AccountPD", accountPD); - } - - public long getUseId() { - return read("UserID", 0L); - } - public void setUseId(long useId) { - save("UserID", useId); - } - - public String getUseName() { - return read("UserName", ""); - } - public void setUseName(String useName) { - save("UserName", useName); - } - - - /* 同步 */ - public boolean isAutoSync() { - return read("autoSync", true); - } - - public void setAutoSync(boolean autoSync) { - save("autoSync", autoSync); - } - - public boolean isAutoSyncOnWifi() { - return read("autoSyncOnWifi", true); - } - - public void setAutoSyncOnWifi(boolean syncOnWifi) { - save("autoSyncOnWifi", syncOnWifi); - } - -// public String getAutoSyncFrequency() { -// return read("autoSyncFrequency", ""); -// } -// public void setAutoSyncFrequency(String syncFrequency) { -// save("autoSyncFrequency", syncFrequency); -// } - - public int getAutoSyncFrequency() { - return read("autoSyncFrequency", 10); - } - - public void setAutoSyncFrequency(int syncFrequency) { - save("autoSyncFrequency", syncFrequency); - } - - - public boolean isDownImgOnlyWifi() { - return read("DownImgWifi", true); - } - - public void setDownImgWifi(boolean downImgMode) { - save("DownImgWifi", downImgMode); - } - - /* 同步(放弃) */ - public boolean isSyncFirstOpen() { - return read("SyncFirstOpen", true); - } - public void setSyncFirstOpen(boolean syncFirstOpen) { - save("SyncFirstOpen", syncFirstOpen); - } - public boolean isSyncAllStarred() { - return read("SyncAllStarred", false); - } - public void setSyncAllStarred(boolean syncAllStarred) { - save("SyncAllStarred", syncAllStarred); - } - public boolean isHadSyncAllStarred() { - return read("HadSyncAllStarred", false); - } - public void setHadSyncAllStarred(boolean had) { - save("HadSyncAllStarred", had); - } - - /* 操作 */ - public boolean isSysBrowserOpenLink() { - return read("SysBrowserOpenLink", false); - } - - public void setSysBrowserOpenLink(boolean is) { - save("SysBrowserOpenLink", is); - } - - - // 是否有选择过默认的图片浏览器 - public boolean hadAskImageOpenMode() { - return read("HadAskImageOpenMode", false); - } - - public void setHadAskImageOpenMode(boolean hadAskImageOpenMode) { - save("HadAskImageOpenMode", hadAskImageOpenMode); - } - - - //是否为滚动标记为已读 - public boolean isScrollMark() { - return read("ScrollMark", false); - } - - public void setScrollMark(boolean scrollMark) { - save("ScrollMark", scrollMark); - } - - - /* 数据 */ - public int getClearBeforeDay() { - return read("ClearBeforeDay", 7); - } - public void setClearBeforeDay(int clearBeforeDay) { - save("ClearBeforeDay", clearBeforeDay); - } - - - /* 主题 */ - public boolean isAutoToggleTheme() { - return read("AutoToggleTheme", true); - } - - public void setAutoToggleTheme(boolean is) { - save("AutoToggleTheme", is); - } - - public int getThemeMode() { - return read("ThemeMode", App.Theme_Day); - } - - public void setThemeMode(int themeMode) { - save("ThemeMode", themeMode); - } - - -// public String getNightThemeStartTime() { -// return read("NightThemeStartTime", "20:00"); -// } -// public void setNightThemeThemeStartTime(String nightThemeStartTime) { -// save("NightThemeStartTime", nightThemeStartTime); -// } -// -// public String getNightThemeEndTime() { -// return read("NightThemeEndTime", "07:00"); -// } -// public void setNightThemeEndTime(String nightThemeEndTime) { -// save("NightThemeEndTime", nightThemeEndTime); -// } - - - public boolean isInoreaderProxy() { - return read("InoreaderProxy", false); - } - public void setInoreaderProxy(boolean proxyMode) { - save("InoreaderProxy", proxyMode); - } - - public String getInoreaderProxyHost() { - return read("InoreaderProxyHost", "https://"); - } - - public void setInoreaderProxyHost(String host) { - save("InoreaderProxyHost", host); - } - - public int getStreamStatus() { - return read("StreamStatus", 0); - } - public void setStreamStatus(int streamStatus) { - save("StreamStatus", streamStatus); - } - - public String getStreamState() { - return read("ListState", "Unread"); - } - public void setStreamState(String listState) { - save("ListState", listState); - } - - - public String getStreamId() { - return read("listTagId", ""); - } - public void setStreamId(String listTagId) { - save("listTagId", listTagId); - } - - public boolean isOrderTagFeed() { // StreamPrefs - return read("OrderTagFeed", true); - } - public void setOrderTagFeed(boolean is) { - save("OrderTagFeed", is); - } - - -// public String getDefaultGroupName() { -// return read("defaultGroupName", "未分组"); -// } -// -// public void setDefaultGroupName(String defaultGroupName) { -// save("defaultGroupName", defaultGroupName); -// } -// -// -// public long getNewestItemTimestampUsec() { -// return read("newestItemTimestampUsec", 0L); -// } -// -// public void setNewestItemTimestampUsec(long newestItemTimestampUsec) { -// save("newestItemTimestampUsec", newestItemTimestampUsec); -// } - - - public String getUserAgent() { - return read("UserAgent", null); - } - - public void setUserAgent(String userAgent) { - save("UserAgent", userAgent); - } - -} diff --git a/app/src/main/java/me/wizos/loread/db/Article.java b/app/src/main/java/me/wizos/loread/db/Article.java index b58a0ba..a628ae3 100644 --- a/app/src/main/java/me/wizos/loread/db/Article.java +++ b/app/src/main/java/me/wizos/loread/db/Article.java @@ -1,93 +1,58 @@ package me.wizos.loread.db; -import org.greenrobot.greendao.annotation.Entity; -import org.greenrobot.greendao.annotation.Generated; -import org.greenrobot.greendao.annotation.Id; -import org.greenrobot.greendao.annotation.Index; -import org.greenrobot.greendao.annotation.NotNull; -import me.wizos.loread.net.Api; - -@Entity -public class Article { - - @Id - @NotNull - @Index +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import org.jetbrains.annotations.NotNull; + +import me.wizos.loread.App; + +/** + * // private Integer preference = 0; // 偏好(点击):0是初始状态,1是不喜欢,2是喜欢 + * // private Integer predict = 0; //推断结果: 0是初始状态,1是不喜欢,2是喜欢 + * Created by Wizos on 2020/3/17. + */ +@Entity(primaryKeys = {"id","uid"}, + indices = { @Index({"id"}),@Index({"uid"}),@Index({"title"}),@Index({"feedId","uid"}),@Index({"readStatus"}),@Index({"starStatus"}),@Index({"saveStatus"})}, + foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "uid") + ) +public class Article implements Cloneable{ + @NonNull private String id; - // crawlTimeMsec和timetampusec是相同的日期,第一个是毫秒,第二个是微秒 - @Index - private Long crawlTimeMsec; - @Index - private Long timestampUsec; - private String categories; - @Index + @NonNull + private String uid; private String title; - @Index - private Long published; - @Index - private Long updated; - private Long starred; - private String enclosure; - private String canonical; - private String alternate; - private String summary; private String content; + private String summary; + private String image; + private String enclosure; // 包含图片,视频等多媒体信息 + + private String feedId; + private String feedTitle; private String author; - @Index - @NotNull - private Integer readStatus = Api.UNREAD; - @Index + private String link = ""; + private long pubDate; + private long crawlDate; + private int readStatus = App.STATUS_UNREAD; + private int starStatus = App.STATUS_UNSTAR; + private int saveStatus = App.STATUS_NOT_FILED; + private long readUpdated; + private long starUpdated; + + @NotNull - private Integer starStatus = Api.UNSTAR; - private String readState; - private String starState; - private String saveDir; - private String imgState; - private String coverSrc; - @Index - private String originStreamId; - private String originTitle; - private String originHtmlUrl; - - - @Generated(hash = 260683293) - public Article(@NotNull String id, Long crawlTimeMsec, Long timestampUsec, String categories, - String title, Long published, Long updated, Long starred, String enclosure, - String canonical, String alternate, String summary, String content, String author, - @NotNull Integer readStatus, @NotNull Integer starStatus, String readState, - String starState, String saveDir, String imgState, String coverSrc, String originStreamId, - String originTitle, String originHtmlUrl) { - this.id = id; - this.crawlTimeMsec = crawlTimeMsec; - this.timestampUsec = timestampUsec; - this.categories = categories; - this.title = title; - this.published = published; - this.updated = updated; - this.starred = starred; - this.enclosure = enclosure; - this.canonical = canonical; - this.alternate = alternate; - this.summary = summary; - this.content = content; - this.author = author; - this.readStatus = readStatus; - this.starStatus = starStatus; - this.readState = readState; - this.starState = starState; - this.saveDir = saveDir; - this.imgState = imgState; - this.coverSrc = coverSrc; - this.originStreamId = originStreamId; - this.originTitle = originTitle; - this.originHtmlUrl = originHtmlUrl; + public String getUid() { + return uid; } - @Generated(hash = 742516792) - public Article() { + public void setUid(String uid) { + this.uid = uid; } + @NotNull public String getId() { return this.id; } @@ -96,30 +61,6 @@ public void setId(String id) { this.id = id; } - public Long getCrawlTimeMsec() { - return this.crawlTimeMsec; - } - - public void setCrawlTimeMsec(Long crawlTimeMsec) { - this.crawlTimeMsec = crawlTimeMsec; - } - - public Long getTimestampUsec() { - return this.timestampUsec; - } - - public void setTimestampUsec(Long timestampUsec) { - this.timestampUsec = timestampUsec; - } - - public String getCategories() { - return this.categories; - } - - public void setCategories(String categories) { - this.categories = categories; - } - public String getTitle() { return this.title; } @@ -128,28 +69,28 @@ public void setTitle(String title) { this.title = title; } - public Long getPublished() { - return this.published; + public String getContent() { + return this.content; } - public void setPublished(Long published) { - this.published = published; + public void setContent(String content) { + this.content = content; } - public Long getUpdated() { - return this.updated; + public String getSummary() { + return this.summary; } - public void setUpdated(Long updated) { - this.updated = updated; + public void setSummary(String summary) { + this.summary = summary; } - public Long getStarred() { - return this.starred; + public String getImage() { + return this.image; } - public void setStarred(Long starred) { - this.starred = starred; + public void setImage(String image) { + this.image = image; } public String getEnclosure() { @@ -160,36 +101,20 @@ public void setEnclosure(String enclosure) { this.enclosure = enclosure; } - public String getCanonical() { - return this.canonical; + public String getFeedId() { + return this.feedId; } - public void setCanonical(String canonical) { - this.canonical = canonical; + public void setFeedId(String feedId) { + this.feedId = feedId; } - public String getAlternate() { - return this.alternate; + public String getFeedTitle() { + return this.feedTitle; } - public void setAlternate(String alternate) { - this.alternate = alternate; - } - - public String getSummary() { - return this.summary; - } - - public void setSummary(String summary) { - this.summary = summary; - } - - public String getContent() { - return this.content; - } - - public void setContent(String content) { - this.content = content; + public void setFeedTitle(String feedTitle) { + this.feedTitle = feedTitle; } public String getAuthor() { @@ -200,83 +125,100 @@ public void setAuthor(String author) { this.author = author; } - public String getReadState() { - return this.readState; - } - - public void setReadState(String readState) { - this.readState = readState; - } - - public String getStarState() { - return this.starState; + public String getLink() { + return this.link; } - public void setStarState(String starState) { - this.starState = starState; + public void setLink(String link) { + this.link = link; } - public String getSaveDir() { - return this.saveDir; + public long getPubDate() { + return this.pubDate; } - public void setSaveDir(String saveDir) { - this.saveDir = saveDir; + public void setPubDate(long pubDate) { + this.pubDate = pubDate; } - public String getImgState() { - return this.imgState; + public long getCrawlDate() { + return this.crawlDate; } - public void setImgState(String imgState) { - this.imgState = imgState; + public void setCrawlDate(long crawlDate) { + this.crawlDate = crawlDate; } - public String getCoverSrc() { - return this.coverSrc; + public int getReadStatus() { + return this.readStatus; } - public void setCoverSrc(String coverSrc) { - this.coverSrc = coverSrc; + public void setReadStatus(int readStatus) { + this.readStatus = readStatus; } - public String getOriginStreamId() { - return this.originStreamId; + public int getStarStatus() { + return this.starStatus; } - public void setOriginStreamId(String originStreamId) { - this.originStreamId = originStreamId; + public void setStarStatus(int starStatus) { + this.starStatus = starStatus; } - public String getOriginTitle() { - return this.originTitle; + public int getSaveStatus() { + return this.saveStatus; } - public void setOriginTitle(String originTitle) { - this.originTitle = originTitle; + public void setSaveStatus(int saveStatus) { + this.saveStatus = saveStatus; } - public String getOriginHtmlUrl() { - return this.originHtmlUrl; + public long getReadUpdated() { + return readUpdated; } - public void setOriginHtmlUrl(String originHtmlUrl) { - this.originHtmlUrl = originHtmlUrl; + public void setReadUpdated(long readUpdated) { + this.readUpdated = readUpdated; } - public Integer getReadStatus() { - return this.readStatus; + public long getStarUpdated() { + return starUpdated; } - public Integer getStarStatus() { - return this.starStatus; + public void setStarUpdated(long starUpdated) { + this.starUpdated = starUpdated; } - public void setReadStatus(Integer readStatus) { - this.readStatus = readStatus; + @Override + public Object clone(){ + try{ + return super.clone(); //浅复制 + }catch(CloneNotSupportedException e) { + e.printStackTrace(); + } + return this; } - - public void setStarStatus(Integer starStatus) { - this.starStatus = starStatus; + @Override + public String toString() { + return "Article{" + + "id='" + id + '\'' + + ", uid='" + uid + '\'' + + ", title='" + title + '\'' + + ", summary='" + summary + '\'' + + ", image='" + image + '\'' + + ", enclosure='" + enclosure + '\'' + + ", feedId='" + feedId + '\'' + + ", feedTitle='" + feedTitle + '\'' + + ", author='" + author + '\'' + + ", link='" + link + '\'' + + ", pubDate=" + pubDate + + ", crawlDate=" + crawlDate + + ", readStatus=" + readStatus + + ", starStatus=" + starStatus + + ", saveStatus=" + saveStatus + + ", readUpdated=" + readUpdated + + ", starUpdated=" + starUpdated + + ", content='" + content + + '}'; } } diff --git a/app/src/main/java/me/wizos/loread/db/ArticleDao.java b/app/src/main/java/me/wizos/loread/db/ArticleDao.java new file mode 100644 index 0000000..a80887d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/ArticleDao.java @@ -0,0 +1,348 @@ +package me.wizos.loread.db; + +import androidx.paging.DataSource; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.RawQuery; +import androidx.room.Transaction; +import androidx.room.Update; +import androidx.sqlite.db.SupportSQLiteQuery; + +import java.util.List; + +import me.wizos.loread.App; + +/** + * DataSource的三个子类: + * PositionalDataSource: 主要用于加载数据可数有限的数据。比如加载本地数据库,这种情况下用户可以通过比如说像通讯录按姓的首字母查询的情况。能够跳转到任意的位置。 + * ItemKeyedDataSource:主要用于加载逐渐增加的数据。比如说网络请求的数据随着不断的请求得到的数据越来越多。然后它适用的情况就是通过N-1item的数据来获取Nitem数据的情况。比如说Github的api。 + * PageKeyedDataSource:这个和ItemKeyedDataSource有些相似,都是针对那种不断增加的数据。这里网络请求得到数据是分页的。比如说知乎日报的news的api。 + * + * 从 Read 属性的4个值(Readed, UnRead, UnReading, All), Star 属性的3个类型(Stared, UnStar, All)中,抽出 UnRead(含UnReading), Stared, All 3个快捷状态,供用户在主页面切换时使用 + * 根据 StreamId 来获取文章,可从2个属性( Categories[针对Tag], OriginStreamId[针对Feed] )上,共4个变化上(All, TTRSSCategoryItem, NoTag, TTRSSFeedItem)来获取文章。 + * 据 StreamState 也是从2个属性(ReadState, StarState)的3个快捷状态 ( UnRead[含UnReading], Stared, All ) 来获取文章。 + * 所以文章列表页会有6种组合:某个 Categories 内的 UnRead[含UnReading], Stared, All。某个 OriginStreamId 内的 UnRead[含UnReading], Stared, All。 + */ +@Dao +public interface ArticleDao { + @Query("SELECT * FROM article WHERE uid = :uid AND id = :id LIMIT 1") + Article getById(String uid, String id); + +// @Query("SELECT * FROM article WHERE uid = :uid " + +// "ORDER BY crawlDate DESC,pubDate DESC") +// Cursor getAll2(String uid); +// @Query("SELECT * FROM article " + +// "WHERE uid = :uid " + +// "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR (article.readStatus = " + App.STATUS_READED +" AND article.readUpdated > :timeMillis) ) " + +// "ORDER BY crawlDate DESC,pubDate DESC") +// DataSource.Factory getUnread2(String uid,long timeMillis); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND crawlDate < :timeMillis " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getAll(String uid,long timeMillis); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND crawlDate < :timeMillis " + + "AND (article.starStatus = " + App.STATUS_STARED + " OR (article.starStatus = " + App.STATUS_UNSTAR +" AND article.starUpdated > :timeMillis))" + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getStared(String uid,long timeMillis); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND crawlDate < :timeMillis " + + "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR (article.readStatus = " + App.STATUS_READED +" AND article.readUpdated > :timeMillis)) " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getUnread(String uid,long timeMillis); + + @Query("SELECT article.* FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND FeedCategory.categoryId = :categoryId " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getAllByCategoryId(String uid, String categoryId, long timeMillis); + @Query("SELECT article.* FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND crawlDate < :timeMillis " + + "AND FeedCategory.categoryId = :categoryId " + + "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR (article.readStatus = " + App.STATUS_READED +" AND article.readUpdated > :timeMillis) ) " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getUnreadByCategoryId(String uid, String categoryId,long timeMillis); + @Query("SELECT article.* FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND FeedCategory.categoryId = :categoryId " + + "AND (article.starStatus = " + App.STATUS_STARED + " OR (article.starStatus = " + App.STATUS_UNSTAR +" AND article.starUpdated > :timeMillis) )" + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getStaredByCategoryId(String uid, String categoryId, long timeMillis); + + + @Query("SELECT article.* FROM article " + + "LEFT JOIN ArticleTag ON (article.uid = ArticleTag.uid AND article.id = ArticleTag.articleId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND ArticleTag.tagId = :tagId " + + "AND (article.starStatus = " + App.STATUS_STARED + " OR (article.starStatus = " + App.STATUS_UNSTAR +" AND article.starUpdated > :timeMillis) )" + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getStaredByTagId(String uid, String tagId, long timeMillis); + + @Query("SELECT article.* FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND FeedCategory.categoryId is NULL " + + "ORDER BY crawlDate,pubDate DESC") + DataSource.Factory getAllByUncategory(String uid, long timeMillis); + @Query("SELECT article.* FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND FeedCategory.categoryId is NULL " + + "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR (article.readStatus = " + App.STATUS_READED +" AND article.readUpdated > :timeMillis) ) " + + "ORDER BY crawlDate,pubDate DESC") + DataSource.Factory getUnreadByUncategory(String uid, long timeMillis); + @Query("SELECT article.* FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND FeedCategory.categoryId is NULL " + + "AND (article.starStatus = " + App.STATUS_STARED + " OR (article.starStatus = " + App.STATUS_UNSTAR +" AND article.starUpdated > :timeMillis) ) " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getStaredByUncategory(String uid, long timeMillis); + + @Query("SELECT article.* FROM article " + + "LEFT JOIN ArticleTag ON (article.uid = articletag.uid AND article.id = articletag.articleId)" + + "WHERE article.uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND articletag.tagId is NULL " + + "AND (article.starStatus = " + App.STATUS_STARED + " OR (article.starStatus = " + App.STATUS_UNSTAR +" AND article.starUpdated > :timeMillis) ) " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getStaredByUnTag(String uid, long timeMillis); + + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND feedId = :feedId " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getAllByFeedId(String uid, String feedId, long timeMillis); + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND feedId = :feedId " + + "AND (readStatus = " + App.STATUS_UNREAD + " OR readStatus = " + App.STATUS_UNREADING + " OR (article.readStatus = " + App.STATUS_READED +" AND article.readUpdated > :timeMillis) ) " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getUnreadByFeedId(String uid, String feedId, long timeMillis); + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND article.crawlDate < :timeMillis " + + "AND feedId = :feedId " + + "AND (starStatus = " + App.STATUS_STARED + " OR (article.starStatus = " + App.STATUS_UNSTAR +" AND article.starUpdated > :timeMillis) ) " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getStaredByFeedId(String uid, String feedId, long timeMillis); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND title LIKE :keyword " + + "ORDER BY crawlDate DESC,pubDate DESC") + DataSource.Factory getAllByKeyword(String uid, String keyword); + +// @Query("DELETE FROM article WHERE uid = :uid AND pubDate < :timeMillis") +// void clearPubDate(String uid,long timeMillis); + +// @Query("SELECT * FROM article " + +// "WHERE uid = :uid " + +// "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR article.starStatus = " + App.STATUS_STARED + ") " + +// "ORDER BY crawlDate DESC,pubDate DESC") +// List
getValuable(String uid); + +// @Query("SELECT article.* FROM article " + +// "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + +// "WHERE article.uid = :uid " + +// "AND FeedCategory.categoryId is NULL " + +// "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR article.starStatus = " + App.STATUS_STARED + ") " + +// "ORDER BY crawlDate,pubDate DESC") +// Cursor getValuableByUnCategory(String uid); + +// @Query("SELECT article.* FROM article " + +// "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + +// "WHERE article.uid = :uid " + +// "AND FeedCategory.categoryId = :categoryId " + +// "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + " OR article.starStatus = " + App.STATUS_STARED + ") " + +// "ORDER BY crawlDate DESC,pubDate DESC") +// Cursor getValuableByCategoryId(String uid, String categoryId); + + + @Query("SELECT * FROM article WHERE uid = :uid") + List
getAllNoOrder(String uid); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND crawlDate > :timeMillis ") + List
getAllNoOrder(String uid,long timeMillis); + + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND article.starStatus = " + App.STATUS_STARED) + List
getStaredNoOrder(String uid); + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + ") ") + List
getUnreadNoOrder(String uid); + + @Query("SELECT count(1) FROM article " + + "WHERE uid = :uid " + + "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + ") ") + int getUnreadCount(String uid); + + @Query("SELECT count(1) FROM article " + + "WHERE uid = :uid " + + "AND article.starStatus = " + App.STATUS_STARED ) + int getStarCount(String uid); + + @Query("SELECT count(1) FROM article " + + "WHERE uid = :uid " ) + int getAllCount(String uid); + + @Query("SELECT count(1) FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND FeedCategory.categoryId is NULL " + + "AND (article.readStatus = " + App.STATUS_UNREAD + " OR article.readStatus = " + App.STATUS_UNREADING + ")") + int getUncategoryUnreadCount(String uid); + + @Query("SELECT count(1) FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND FeedCategory.categoryId is NULL " + + "AND article.starStatus = " + App.STATUS_STARED) + int getUncategoryStarCount(String uid); + + @Query("SELECT count(1) FROM article " + + "LEFT JOIN FeedCategory ON (article.uid = FeedCategory.uid AND article.feedId = FeedCategory.feedId)" + + "WHERE article.uid = :uid " + + "AND FeedCategory.categoryId is NULL ") + int getUncategoryAllCount(String uid); + + @Query("SELECT readUpdated FROM article " + + "WHERE uid = :uid " + + "ORDER BY readUpdated DESC LIMIT 1") + long getLastReadTimeMillis(String uid); + + @Query("SELECT starUpdated FROM article " + + "WHERE uid = :uid " + + "ORDER BY starUpdated DESC LIMIT 1") + long getLastStarTimeMillis(String uid); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND (article.readStatus = " + App.STATUS_UNREADING + " OR article.saveStatus !=" + App.STATUS_NOT_FILED + ")") + List
getBackup(String uid); + + // TODO: 2020/5/1 文章要加上是否已经被 Readability 的标志 + @Query("SELECT article.* FROM article " + + "LEFT JOIN Feed ON (article.uid = Feed.uid AND article.feedId = Feed.id) " + + "WHERE article.uid = :uid " + + "AND article.crawlDate = :timeMillis " + + "AND Feed.displayMode = 1 ") + List
getNeedReadability(String uid, long timeMillis); + + @Query("SELECT article.* FROM article " + + "LEFT JOIN ArticleTag ON (article.uid = articletag.uid AND article.id = articletag.articleId) " + + "WHERE article.uid = :uid " + + "AND article.crawlDate >= :timeMillis " + + "AND article.starStatus = " + App.STATUS_STARED + " " + + "AND article.feedId not Null " + + "AND articletag.tagId is Null ") + List
getNotTagStar(String uid, long timeMillis); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND id in (:ids)") + List
getArticles(String uid, List ids); + + @RawQuery + List
getActionRuleArticlesRaw(SupportSQLiteQuery query); + + @RawQuery + List getActionRuleArticlesRaw2(SupportSQLiteQuery query); + + + +// @Query("SELECT * FROM article WHERE uid = :uid AND feedId = :feedId " + +// "AND (readStatus = " + App.STATUS_UNREAD + " OR readStatus = " + App.STATUS_UNREADING + " OR article.starStatus = " + App.STATUS_STARED + ") " + +// "ORDER BY crawlDate DESC,pubDate DESC") +// Cursor getValuableByFeedId(String uid, String feedId); + + @Query("SELECT * FROM article WHERE uid = :uid ORDER BY id DESC LIMIT 1") + Article getLastArticle(String uid); + + @Query("SELECT link FROM article " + + "WHERE uid = :uid " + + "GROUP BY link HAVING COUNT(*) > 1") //,title + List getDuplicatesLink(String uid); + + @Query("SELECT * FROM article " + + "WHERE uid = :uid " + + "AND link = :link " + + "ORDER BY crawlDate DESC") + List
getDuplicates(String uid, String link); + + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(Article... articles); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(List
articles); + + @Update + @Transaction + void update(Article... articles); + + @Update + @Transaction + void update(List
articles); + + /** + * 将上次操作之后所有新同步文章的爬取时间都重置 + * @param uid + * @param lastMarkTimeMillis + * @param targetTimeMillis + */ + @Query("UPDATE Article SET crawlDate = :targetTimeMillis WHERE uid = :uid AND crawlDate > :lastMarkTimeMillis ") + void updateIdleCrawlDate(String uid,long lastMarkTimeMillis, long targetTimeMillis); + + @Query("DELETE FROM article WHERE uid = :uid AND feedId = :feedId AND starStatus = " + App.STATUS_UNSTAR) + void deleteUnStarByFeedId(String uid, String feedId); + + @Delete + @Transaction + void delete(Article... articles); + + @Delete + @Transaction + void delete(List
articles); + + @Query("SELECT * FROM article WHERE uid = :uid AND readStatus = " + App.STATUS_READED + " AND starStatus = " + App.STATUS_UNSTAR + " AND saveStatus = " + App.STATUS_TO_BE_FILED + " AND crawlDate < :time" ) + List
getReadedUnstarBeFiledLtTime(String uid, long time); + + @Query("SELECT * FROM article WHERE uid = :uid AND readStatus = " + App.STATUS_READED + " AND starStatus = " + App.STATUS_STARED + " AND saveStatus = " + App.STATUS_TO_BE_FILED + " AND crawlDate < :time" ) + List
getReadedStaredBeFiledLtTime(String uid, long time); + + @Query("SELECT * FROM article WHERE uid = :uid AND readStatus = " + App.STATUS_READED + " AND starStatus = " + App.STATUS_UNSTAR + " AND crawlDate < :time" ) + List
getReadedUnstarLtTime(String uid, long time); + + @Query("DELETE FROM article WHERE uid = (:uid)") + void clear(String... uid); +} diff --git a/app/src/main/java/me/wizos/loread/db/ArticleTag.java b/app/src/main/java/me/wizos/loread/db/ArticleTag.java new file mode 100644 index 0000000..d837c94 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/ArticleTag.java @@ -0,0 +1,70 @@ +package me.wizos.loread.db; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import org.jetbrains.annotations.NotNull; + +import static androidx.room.ForeignKey.CASCADE; + +/** + * Created by Wizos on 2020/3/17. + */ +@Entity(primaryKeys = {"uid","articleId","tagId"}, + indices = {@Index({"uid"}),@Index({"uid","articleId"}),@Index({"uid","tagId"})}, + foreignKeys = {@ForeignKey(entity = User.class, parentColumns = "id", childColumns = "uid", onDelete = CASCADE) } +) +public class ArticleTag { + @NonNull + private String uid; + @NonNull + private String articleId; + @NonNull + private String tagId; + + public ArticleTag(@NonNull String uid, @NonNull String articleId, @NonNull String tagId){ + this.uid = uid; + this.articleId = articleId; + this.tagId = tagId; + } + + @Override + public String toString() { + return "ArticleTag{" + + "uid=" + uid + + ", articleId='" + articleId + '\'' + + ", tagId='" + tagId + '\'' + + '}'; + } + + @NonNull + public String getUid() { + return uid; + } + + public void setUid(@NonNull String uid) { + this.uid = uid; + } + + @NotNull + public String getTagId() { + return this.tagId; + } + + + public void setTagId(@NotNull String tagId) { + this.tagId = tagId; + } + + + @NotNull + public String getArticleId() { + return this.articleId; + } + + public void setArticleId(@NotNull String articleId) { + this.articleId = articleId; + } +} diff --git a/app/src/main/java/me/wizos/loread/db/ArticleTagDao.java b/app/src/main/java/me/wizos/loread/db/ArticleTagDao.java new file mode 100644 index 0000000..b4b362c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/ArticleTagDao.java @@ -0,0 +1,67 @@ +package me.wizos.loread.db; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Transaction; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface ArticleTagDao { + @Query("SELECT * FROM articletag WHERE uid = :uid") + List getAll(String uid); + + @Query("SELECT * FROM articletag WHERE uid = :uid AND articleId = :articleId") + List getByArticleId(String uid, String articleId); + + @Query("SELECT * FROM articletag WHERE uid = :uid AND tagId = :tagId") + List getByTagId(String uid, String tagId); + + @Query("SELECT articletag.* FROM articletag " + + "LEFT JOIN Article ON (articletag.uid = article.uid AND articletag.articleId = article.id) " + + "WHERE articletag.uid = :uid " + + "AND tagId is Null") + List getNotArticles(String uid); + + + @Query("SELECT count(*) FROM articletag WHERE uid = :uid AND tagId = :tagId") + int getCountByTagId(String uid, String tagId); + + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(ArticleTag... articleTags); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(List feedCategories); + + @Update + @Transaction + void update(ArticleTag... feedCategories); + + @Query("UPDATE articletag SET tagId = :newTagId where uid = :uid AND tagId = :oldTagId") + void updateCategoryId(String uid, String oldTagId, String newTagId); + + + @Delete + @Transaction + void delete(ArticleTag articleTag); + + @Delete + @Transaction + void delete(List articleTags); + + @Query("DELETE FROM articletag WHERE uid = :uid AND articleId = :articleId") + void deleteByArticleId(String uid, String articleId); + + @Query("DELETE FROM articletag WHERE uid = (:uid) AND tagId = :tagId") + void deleteByTagId(String uid, String tagId); + + @Query("DELETE FROM articletag WHERE uid = (:uid)") + void clear(String... uid); +} diff --git a/app/src/main/java/me/wizos/loread/db/Category.java b/app/src/main/java/me/wizos/loread/db/Category.java new file mode 100644 index 0000000..a215e09 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/Category.java @@ -0,0 +1,103 @@ +package me.wizos.loread.db; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Ignore; +import androidx.room.Index; + +import me.wizos.loread.bean.feedly.CategoryItem; + +import static androidx.room.ForeignKey.CASCADE; + +/** + indices = {@Index({"id","uid","title"})} , + + * Created by Wizos on 2020/3/17. + */ + +@Entity(primaryKeys = {"id","uid"}, + indices = {@Index({"id"}),@Index({"uid"}),@Index({"title"})}, + foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "uid", onDelete = CASCADE)) +public class Category { + @NonNull + private String id; + @NonNull + private String uid; + private String title; + + private int unreadCount; + private int starCount; + private int allCount; + // 添加此标记后不会生成数据库表的列 + @Ignore + public boolean isExpand; + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getUnreadCount() { + return this.unreadCount; + } + + public void setUnreadCount(int unreadCount) { + this.unreadCount = unreadCount; + } + + public int getStarCount() { + return this.starCount; + } + + public void setStarCount(int starCount) { + this.starCount = starCount; + } + + public int getAllCount() { + return this.allCount; + } + + public void setAllCount(int allCount) { + this.allCount = allCount; + } + + public CategoryItem convert2CategoryItem() { + CategoryItem category = new CategoryItem(); + category.setId(id); + category.setLabel(title); + return category; + } + + @Override + public String toString() { + return "Category{" + + "id='" + id + '\'' + + ", uid='" + uid + '\'' + + ", title='" + title + '\'' + + ", unreadCount=" + unreadCount + + ", starCount=" + starCount + + ", allCount=" + allCount + + ", isExpand=" + isExpand + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/db/CategoryDao.java b/app/src/main/java/me/wizos/loread/db/CategoryDao.java new file mode 100644 index 0000000..d546aff --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/CategoryDao.java @@ -0,0 +1,71 @@ +package me.wizos.loread.db; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Transaction; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface CategoryDao { + @Query("SELECT * FROM category WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + List getAll(String uid); + + + + @Query("SELECT id,title,unreadCount as count FROM category WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + List getCategoriesUnreadCount(String uid); + + @Query("SELECT id,title,starCount as count FROM category WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + List getCategoriesStarCount(String uid); + + @Query("SELECT id,title,allCount as count FROM category WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + List getCategoriesAllCount(String uid); + + + @Query("SELECT * FROM category WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + LiveData> getAllLiveData(String uid); + + + @Query("SELECT category.* FROM category " + + "LEFT JOIN feedcategory ON (category.uid = feedcategory.uid AND category.id = feedcategory.categoryId) " + + "WHERE category.uid = :uid AND FeedCategory.feedId = :feedId " + + "ORDER BY title COLLATE NOCASE ASC") + List getByFeedId(String uid, String feedId); + + @Query("SELECT * FROM category WHERE uid = :uid AND id = :id LIMIT 1") + Category getById(String uid, String id); + + @Query("SELECT * FROM categoryview WHERE uid = :uid" ) + List getCategoriesRealTimeCount(String uid); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(Category... categories); + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(List categories); + + @Query("UPDATE category SET id = :newId where uid = :uid AND id = :oldId") + void updateId(String uid, String oldId, String newId); + + @Update + @Transaction + void update(Category... categories); + + @Update + @Transaction + void update(List categories); + + @Delete + @Transaction + void delete(Category... categories); + + @Query("DELETE FROM category WHERE uid = (:uid)") + void clear(String... uid); +} diff --git a/app/src/main/java/me/wizos/loread/db/CategoryView.java b/app/src/main/java/me/wizos/loread/db/CategoryView.java new file mode 100644 index 0000000..e4bb84f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/CategoryView.java @@ -0,0 +1,22 @@ +package me.wizos.loread.db; + +import androidx.room.DatabaseView; + +/** + indices = {@Index({"id","uid","title"})} , + * Created by Wizos on 2020/3/17. + */ + +@DatabaseView( + "SELECT CATEGORY.uid,id,title,UNREAD_SUM AS unreadCount,STAR_SUM AS starCount,ALL_SUM AS allCount FROM CATEGORY " + + "LEFT JOIN " + + "(" + + " SELECT uid,categoryId,sum(UNREAD_SUM) AS UNREAD_SUM,sum(STAR_SUM) AS STAR_SUM,sum(ALL_SUM) AS ALL_SUM FROM " + + " (" + + " SELECT FEEDCATEGORY.uid,categoryId,feedId,UNREAD_SUM,STAR_SUM,ALL_SUM FROM FEEDCATEGORY " + + " LEFT JOIN (SELECT uid,id,unreadCount AS UNREAD_SUM, starCount AS STAR_SUM, allCount AS ALL_SUM FROM feedview) AS FEED ON feedcategory.uid = FEED.uid AND feedcategory.feedId = FEED.ID " + + " ) AS FeedCategoryCount GROUP BY uid,CATEGORYID " + + ") AS C ON CATEGORY.uid = C.uid AND CATEGORY.ID = C.CATEGORYID " +) +public class CategoryView extends Category{ +} diff --git a/app/src/main/java/me/wizos/loread/db/Collection.java b/app/src/main/java/me/wizos/loread/db/Collection.java new file mode 100644 index 0000000..e021125 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/Collection.java @@ -0,0 +1,50 @@ +package me.wizos.loread.db; + +import me.wizos.loread.bean.feedly.CategoryItem; + +// Category 和 Feed 的抽象 +public class Collection { + private String id; + private String title; + private int count; + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getCount() { + return this.count; + } + + public void setCount(int count) { + this.count = count; + } + + public CategoryItem convert2CategoryItem() { + CategoryItem category = new CategoryItem(); + category.setId(id); + category.setLabel(title); + return category; + } + + @Override + public String toString() { + return "Collection{" + + "id='" + id + '\'' + + ", title='" + title + '\'' + + ", count=" + count + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/db/CoreDB.java b/app/src/main/java/me/wizos/loread/db/CoreDB.java new file mode 100644 index 0000000..0db3fc2 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/CoreDB.java @@ -0,0 +1,329 @@ +package me.wizos.loread.db; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; +import androidx.sqlite.db.SupportSQLiteDatabase; + +import com.socks.library.KLog; + +import java.util.ArrayList; +import java.util.List; + +import me.wizos.loread.App; +import me.wizos.loread.bean.feedly.CategoryItem; +import me.wizos.loread.bean.feedly.input.EditFeed; + +/** + * @Database标签用于告诉系统这是Room数据库对象。 + * entities属性用于指定该数据库有哪些表,若需建立多张表,以逗号相隔开。 + * version属性用于指定数据库版本号,后续数据库的升级正是依据版本号来判断的。 + * 该类需要继承自RoomDatabase,在类中,通过Room.databaseBuilder()结合单例设计模式,完成数据库的创建工作。 + */ +@Database( + entities = {User.class,Article.class,Feed.class,Category.class, FeedCategory.class, Tag.class, ArticleTag.class}, + views = {FeedView.class,CategoryView.class}, + version = 1, + exportSchema = false +) +public abstract class CoreDB extends RoomDatabase { + private static final String DATABASE_NAME = "loreadx.db"; + private static CoreDB databaseInstance; + + public static synchronized void init(Context context) { + if(databaseInstance == null) { + synchronized (CoreDB.class) { // 同步锁,避免多线程时可能 new 出两个实例的情况 + if (databaseInstance == null) { + databaseInstance = Room.databaseBuilder(context.getApplicationContext(), CoreDB.class, DATABASE_NAME) + .addCallback(new Callback() { + @Override + public void onOpen(@NonNull SupportSQLiteDatabase db) { + super.onOpen(db); + KLog.e("创建触发器"); + createTriggers(db); + } + }) +// .addMigrations(MIGRATION_1_2) + .allowMainThreadQueries() + .build(); + } + } + } + } + + public static CoreDB i(){ + if( databaseInstance == null ){ + throw new RuntimeException("CoreBD must init in Application class"); + } + return databaseInstance; + } +// +// static final Migration MIGRATION_1_2 = new Migration(1, 2) { +// @Override +// public void migrate(SupportSQLiteDatabase database) { +// //因为修改了视图所以先删除老的视图 +//// database.execSQL("drop view FeedView"); +//// database.execSQL("drop view CategoryView;"); +// } +// }; + + /** + * 创建触发器 + * 在 INSERT 型触发器中,只有NEW是合法的,NEW 用来表示将要(BEFORE)或已经(AFTER)插入的新数据; + * 在 UPDATE 型触发器中,NEW、OLD可以同时使用,OLD 用来表示将要或已经被修改的原数据,NEW 用来表示将要或已经修改为的新数据; + * 在 DELETE 型触发器中,只有 OLD 才合法,OLD 用来表示将要或已经被删除的原数据; + * 使用方法: NEW.columnName (columnName 为相应数据表某一列名) + * 另外,OLD 是只读的,而 NEW 则可以在触发器中使用 SET 赋值,这样不会再次触发触发器,造成循环调用(如每插入一个学生前,都在其学号前加“2013”)。 + * @param db + */ + private static void createTriggers(SupportSQLiteDatabase db) { + //【当插入文章时】 + String updateFeedAllCountWhenInsertArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedAllCountWhenInsertArticle" + + " AFTER INSERT ON ARTICLE" + + " BEGIN" + + " UPDATE FEED SET ALLCOUNT = ALLCOUNT + 1 WHERE ID IS new.FEEDID AND UID IS new.UID;" + + " END"; + db.execSQL(updateFeedAllCountWhenInsertArticle); + String updateFeedUnreadCountWhenInsertArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedUnreadCountWhenInsertArticle" + + " AFTER INSERT ON ARTICLE" + + " WHEN (new.READSTATUS = 1)" + + " BEGIN" + + " UPDATE FEED SET UNREADCOUNT = UNREADCOUNT + 1 WHERE ID IS new.FEEDID AND UID IS new.UID;" + + " END" ; + db.execSQL(updateFeedUnreadCountWhenInsertArticle); + String updateFeedStarCountWhenInsertArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedStarCountWhenInsertArticle" + + " AFTER INSERT ON ARTICLE" + + " WHEN (new.STARSTATUS = 4)" + + " BEGIN" + + " UPDATE FEED SET STARCOUNT = STARCOUNT + 1 WHERE ID IS new.FEEDID AND UID IS new.UID;" + + " END"; + db.execSQL(updateFeedStarCountWhenInsertArticle); + + + + //【当删除文章时】 + String updateFeedAllCountWhenDeleteArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedAllCountWhenDeleteArticle" + + " AFTER DELETE ON ARTICLE" + + " BEGIN" + + " UPDATE FEED SET ALLCOUNT = ALLCOUNT - 1 WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END"; + db.execSQL(updateFeedAllCountWhenDeleteArticle); + String updateFeedUnreadCountWhenDeleteArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedUnreadCountWhenDeleteArticle" + + " AFTER DELETE ON ARTICLE" + + " WHEN (old.READSTATUS = 1 OR old.READSTATUS = 3)" + + " BEGIN" + + " UPDATE FEED SET UNREADCOUNT = UNREADCOUNT - 1 WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END" ; + db.execSQL(updateFeedUnreadCountWhenDeleteArticle); + String updateFeedStarCountWhenDeleteArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedStarCountWhenDeleteArticle" + + " AFTER DELETE ON ARTICLE" + + " WHEN (old.STARSTATUS = 4)" + + " BEGIN" + + " UPDATE FEED SET STARCOUNT = STARCOUNT - 1 WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END"; + db.execSQL(updateFeedStarCountWhenDeleteArticle); + +// // 当 READSTATUS 状态更新时,标注其更新时间 +// String updatedWhenArticleChange = +// "CREATE TEMP TRIGGER IF NOT EXISTS updatedWhenArticleChange" + +// " AFTER UPDATE OF READSTATUS,STARSTATUS ON ARTICLE" + +// " WHEN old.READSTATUS != new.READSTATUS OR old.STARSTATUS != new.STARSTATUS" + +// " BEGIN" + +// " UPDATE ARTICLE SET updateTime = (strftime('%s','now') || substr(strftime('%f','now'),4))" + +// " WHERE UID IS old.UID AND ID IS old.ID;" + +// " END"; +// db.execSQL(updatedWhenArticleChange); + + // 当 READSTATUS 状态更新时,标注其更新时间 + String updatedWhenReadStatusChange = + "CREATE TEMP TRIGGER IF NOT EXISTS updatedWhenReadStatusChange" + + " AFTER UPDATE OF READSTATUS ON ARTICLE" + + " WHEN old.READSTATUS != new.READSTATUS" + + " BEGIN" + + " UPDATE ARTICLE SET readUpdated = (strftime('%s','now') || substr(strftime('%f','now'),4))" + + " WHERE ID IS old.ID AND UID IS old.UID;" + + " END"; + db.execSQL(updatedWhenReadStatusChange); + // 当 STARSTATUS 状态更新时,标注其更新时间 + String updatedWhenStarStatusChange = + "CREATE TEMP TRIGGER IF NOT EXISTS updatedWhenStarStatusChange" + + " AFTER UPDATE OF STARSTATUS ON ARTICLE" + + " WHEN old.STARSTATUS != new.STARSTATUS" + + " BEGIN" + + " UPDATE ARTICLE SET starUpdated = (strftime('%s','now') || substr(strftime('%f','now'),4))" + + " WHERE ID IS old.ID AND UID IS old.UID;" + + " END"; + db.execSQL(updatedWhenStarStatusChange); + + + // 当updateTime因为 + String readUpdatedNoChange = + "CREATE TEMP TRIGGER IF NOT EXISTS readUpdatedNoChange" + + " AFTER UPDATE OF readUpdated ON ARTICLE" + + " WHEN old.readUpdated > new.readUpdated" + + " BEGIN" + + " UPDATE ARTICLE SET readUpdated = old.readUpdated" + + " WHERE UID IS old.UID AND ID IS old.ID;" + + " END"; + db.execSQL(readUpdatedNoChange); + + // 当updateTime因为 + String starUpdatedNoChange = + "CREATE TEMP TRIGGER IF NOT EXISTS starUpdatedNoChange" + + " AFTER UPDATE OF starUpdated ON ARTICLE" + + " WHEN old.starUpdated > new.starUpdated" + + " BEGIN" + + " UPDATE ARTICLE SET starUpdated = old.starUpdated" + + " WHERE UID IS old.UID AND ID IS old.ID;" + + " END"; + db.execSQL(starUpdatedNoChange); + + + // 标记文章为已读时,更新feed的未读计数 + String updateFeedUnreadCountWhenReadArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedUnreadCountWhenReadArticle" + + " AFTER UPDATE OF READSTATUS ON ARTICLE" + + " WHEN (old.READSTATUS != 2 AND new.READSTATUS = 2)" + + " BEGIN" + + " UPDATE FEED SET UNREADCOUNT = UNREADCOUNT - 1 WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END"; + db.execSQL(updateFeedUnreadCountWhenReadArticle); + // 标记文章为未读时,更新feed的未读计数 + String updateFeedUnreadCountWhenUnreadArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedUnreadCountWhenUnreadArticle" + + " AFTER UPDATE OF READSTATUS ON ARTICLE" + + " WHEN (old.READSTATUS = 2 AND new.READSTATUS != 2 )" + + " BEGIN" + + " UPDATE FEED" + + " SET UNREADCOUNT = UNREADCOUNT + 1" + + " WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END"; + db.execSQL(updateFeedUnreadCountWhenUnreadArticle); + + // 标记文章为加星时,更新feed的计数 + String updateFeedUnreadCountWhenStaredArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedUnreadCountWhenStaredArticle" + + " AFTER UPDATE OF STARSTATUS ON ARTICLE" + + " WHEN (old.STARSTATUS != 4 AND new.STARSTATUS = 4)" + + " BEGIN" + + " UPDATE FEED SET STARCOUNT = STARCOUNT + 1" + + " WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END"; + db.execSQL(updateFeedUnreadCountWhenStaredArticle); + // 标记文章为无星时,更新feed的计数 + String updateFeedUnreadCountWhenUnstarArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateFeedUnreadCountWhenUnstarArticle" + + " AFTER UPDATE OF STARSTATUS ON ARTICLE" + + " WHEN (old.STARSTATUS != 5 AND new.STARSTATUS = 5)" + + " BEGIN" + + " UPDATE FEED SET STARCOUNT = STARCOUNT - 1" + + " WHERE ID IS old.FEEDID AND UID IS old.UID;" + + " END"; + db.execSQL(updateFeedUnreadCountWhenUnstarArticle); + + + // 当feed的总计数加一时,更新tag的总计数 + String updateTagAllCountWhenInsertArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateTagAllCountWhenInsertArticle" + + " AFTER UPDATE OF ALLCOUNT ON FEED" + + " WHEN (new.ALLCOUNT = old.ALLCOUNT + 1)" + + " BEGIN" + + " UPDATE CATEGORY SET ALLCOUNT = ALLCOUNT + 1" + + " WHERE ID IN (select CATEGORYID from FEEDCATEGORY where FEEDID = old.ID AND UID IS old.UID);" + + " END"; + db.execSQL(updateTagAllCountWhenInsertArticle); + // 当feed的总计数减一时,更新tag的总计数 + String updateTagAllCountWhenDeleteArticle = + "CREATE TEMP TRIGGER IF NOT EXISTS updateTagAllCountWhenDeleteArticle" + + " AFTER UPDATE OF ALLCOUNT ON FEED" + + " WHEN (new.ALLCOUNT = old.ALLCOUNT - 1)" + + " BEGIN" + + " UPDATE CATEGORY SET ALLCOUNT = ALLCOUNT - 1" + + " WHERE ID IN (select CATEGORYID from FEEDCATEGORY where FEEDID = old.ID AND UID IS old.UID);" + + " END"; + db.execSQL(updateTagAllCountWhenDeleteArticle); + + + // 当feed的未读计数加一时,更新tag的未读计数 + String updateTagUnreadCountWhenAdd = + "CREATE TEMP TRIGGER IF NOT EXISTS updateTagUnreadCountWhenAdd" + + " AFTER UPDATE OF UNREADCOUNT ON FEED" + + " WHEN (new.UNREADCOUNT = old.UNREADCOUNT + 1)" + + " BEGIN" + + " UPDATE CATEGORY" + + " SET UNREADCOUNT = UNREADCOUNT + 1" + + " WHERE ID IN (select CATEGORYID from FEEDCATEGORY where FEEDID = old.ID AND UID IS old.UID);" + + " END"; + db.execSQL(updateTagUnreadCountWhenAdd); + // 当feed的未读计数减一时,更新tag的未读计数 + String updateTagUnreadCountWhenMinus = + "CREATE TEMP TRIGGER IF NOT EXISTS updateTagUnreadCountWhenMinus" + + " AFTER UPDATE OF UNREADCOUNT ON FEED" + + " WHEN (new.UNREADCOUNT = old.UNREADCOUNT - 1)" + + " BEGIN" + + " UPDATE CATEGORY" + + " SET UNREADCOUNT = UNREADCOUNT - 1" + + " WHERE ID IN (select CATEGORYID from FEEDCATEGORY where FEEDID = old.ID AND UID IS old.UID);" + + " END"; + db.execSQL(updateTagUnreadCountWhenMinus); + + + // 当feed的星标计数加一时,更新tag的星标计数 + String updateTagStarCountWhenAdd = + " CREATE TEMP TRIGGER IF NOT EXISTS updateTagStarCountWhenAdd" + + " AFTER UPDATE OF STARCOUNT ON FEED" + + " WHEN (new.STARCOUNT = old.STARCOUNT + 1)" + + " BEGIN" + + " UPDATE CATEGORY SET STARCOUNT = STARCOUNT + 1" + + " WHERE ID IN (select CATEGORYID from FEEDCATEGORY where FEEDID = old.ID AND UID IS old.UID);" + + " END"; + db.execSQL(updateTagStarCountWhenAdd); + + String updateTagStarCountWhenMinus = + " CREATE TEMP TRIGGER IF NOT EXISTS updateTagStarCountWhenMinus" + + " AFTER UPDATE OF STARCOUNT ON FEED" + + " WHEN (new.STARCOUNT = old.STARCOUNT - 1)" + + " BEGIN" + + " UPDATE CATEGORY SET STARCOUNT = STARCOUNT - 1" + + " WHERE ID IN (select CATEGORYID from FEEDCATEGORY where FEEDID = old.ID AND UID IS old.UID);" + + " END"; + db.execSQL(updateTagStarCountWhenMinus); + } + + + /** + * 我们创建的Dao对象,在这里以抽象方法的形式返回,只需一行代码即可。 + */ + public abstract UserDao userDao(); + public abstract ArticleDao articleDao(); + public abstract FeedDao feedDao(); + public abstract CategoryDao categoryDao(); + public abstract FeedCategoryDao feedCategoryDao(); + //public abstract ArticleFtsDao articleFtsDao(); + + public abstract TagDao tagDao(); + public abstract ArticleTagDao articleTagDao(); + + public void coverFeedCategories(EditFeed editFeed) { + String uid = App.i().getUser().getId(); + List cloudyCategoryItems = editFeed.getCategoryItems(); + ArrayList feedCategories = new ArrayList<>(cloudyCategoryItems.size()); + FeedCategory feedCategory; + for (CategoryItem categoryItem : cloudyCategoryItems) { + feedCategory = new FeedCategory(uid, editFeed.getId(), categoryItem.getId()); + feedCategories.add(feedCategory); + } + + CoreDB.i().feedCategoryDao().deleteByFeedId(uid, editFeed.getId()); + CoreDB.i().feedCategoryDao().insert(feedCategories); + } +} diff --git a/app/src/main/java/me/wizos/loread/db/CorePref.java b/app/src/main/java/me/wizos/loread/db/CorePref.java new file mode 100644 index 0000000..b791dc6 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/CorePref.java @@ -0,0 +1,49 @@ +package me.wizos.loread.db; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.SharedPreferences; + +import me.wizos.loread.App; +import me.wizos.loread.R; + +/** + * @author Wizos + * @date 2016/4/30 + * 内部设置 + */ +public class CorePref { + private static final String TAG = "CorePref"; + private static CorePref coreSharedPreferences; + private static SharedPreferences mySharedPreferences; + private static SharedPreferences.Editor editor; + private CorePref() { + } + + @SuppressLint("CommitPrefEdits") + public static CorePref i() { + // 双重锁定,只有在 mySharedPreferences 还没被初始化的时候才会进入到下一行,然后加上同步锁 + if (coreSharedPreferences == null) { + // 同步锁,避免多线程时可能 new 出两个实例的情况 + synchronized (CorePref.class) { + if (coreSharedPreferences == null) { + coreSharedPreferences = new CorePref(); + mySharedPreferences = App.i().getSharedPreferences(App.i().getString(R.string.app_id), Activity.MODE_PRIVATE); + editor = mySharedPreferences.edit(); + } + } + } + return coreSharedPreferences; + } + + public String getString(String key, String value) { + return mySharedPreferences.getString(key, value); + } + public void putString(String key, String value){ + editor.putString(key, value); //用putString的方法保存数据 + editor.commit(); //提交当前数据 + } + public void remove(String key){ + editor.remove(key).commit(); + } +} diff --git a/app/src/main/java/me/wizos/loread/db/Entry.java b/app/src/main/java/me/wizos/loread/db/Entry.java new file mode 100644 index 0000000..98f9601 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/Entry.java @@ -0,0 +1,22 @@ +package me.wizos.loread.db; + +public class Entry { + private String id; + private String entry; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEntry() { + return entry; + } + + public void setEntry(String entry) { + this.entry = entry; + } +} diff --git a/app/src/main/java/me/wizos/loread/db/Feed.java b/app/src/main/java/me/wizos/loread/db/Feed.java index 9032518..2c2a366 100644 --- a/app/src/main/java/me/wizos/loread/db/Feed.java +++ b/app/src/main/java/me/wizos/loread/db/Feed.java @@ -1,252 +1,149 @@ package me.wizos.loread.db; -import org.greenrobot.greendao.DaoException; -import org.greenrobot.greendao.annotation.Entity; -import org.greenrobot.greendao.annotation.Generated; -import org.greenrobot.greendao.annotation.Id; -import org.greenrobot.greendao.annotation.Index; -import org.greenrobot.greendao.annotation.JoinProperty; -import org.greenrobot.greendao.annotation.NotNull; -import org.greenrobot.greendao.annotation.OrderBy; -import org.greenrobot.greendao.annotation.ToMany; +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import static androidx.room.ForeignKey.CASCADE; + +/** + * Feed 与 Category 是 多对多关系,即一个 Feed 可以存在与多个 Category 中,Category 也可以包含多个 Feed + * Created by Wizos on 2020/3/17. + // @Index({"id", "uid", "title", "feedUrl"}) + @Index({"id"}),@Index({"uid"}), + */ +@Entity( + primaryKeys = {"id","uid"}, + indices = {@Index({"id"}),@Index({"uid"}),@Index({"title"}),@Index({"feedUrl"})}, + foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "uid", onDelete = CASCADE) ) +public class Feed { + @NonNull + private String id; + @NonNull + private String uid; -import java.util.List; + private String title; -import me.wizos.loread.db.dao.ArticleDao; -import me.wizos.loread.db.dao.DaoSession; -import me.wizos.loread.db.dao.FeedDao; + private String feedUrl; + private String htmlUrl; + private String iconUrl; + // 0->rss, 1->readability, 2->link + private int displayMode; -@Entity -public class Feed { + private int unreadCount; + private int starCount; + private int allCount; - @Id - @NotNull - @Index - private String id; + // 记录该文feed什么时候被取消订阅。0为已订阅 + private long state = 0; - @NotNull - private String title; - @Index - private String categoryid; - private String categorylabel; - private String sortid; - private Long firstitemmsec; - private String url; - private String htmlurl; - private String iconurl; - private String openMode; - private Integer unreadCount = 0; - private Long newestItemTimestampUsec; - - @ToMany(joinProperties = { - @JoinProperty(name = "id", referencedName = "categories") - }) - @OrderBy("timestampUsec DESC") - private List
items; - - /** - * Used to resolve relations - */ - @Generated(hash = 2040040024) - private transient DaoSession daoSession; - - /** - * Used for active entity operations. - */ - @Generated(hash = 2085497664) - private transient FeedDao myDao; - - @Generated(hash = 1043569664) - public Feed(@NotNull String id, @NotNull String title, String categoryid, String categorylabel, - String sortid, Long firstitemmsec, String url, String htmlurl, String iconurl, - String openMode, Integer unreadCount, Long newestItemTimestampUsec) { - this.id = id; - this.title = title; - this.categoryid = categoryid; - this.categorylabel = categorylabel; - this.sortid = sortid; - this.firstitemmsec = firstitemmsec; - this.url = url; - this.htmlurl = htmlurl; - this.iconurl = iconurl; - this.openMode = openMode; - this.unreadCount = unreadCount; - this.newestItemTimestampUsec = newestItemTimestampUsec; + public String getUid() { + return uid; } - @Generated(hash = 1810414124) - public Feed() { + public void setUid(String uid) { + this.uid = uid; } public String getId() { return this.id; } + public void setId(String id) { this.id = id; } + public String getTitle() { return this.title; } + public void setTitle(String title) { this.title = title; } - public String getCategoryid() { - return this.categoryid; - } - public void setCategoryid(String categoryid) { - this.categoryid = categoryid; + public String getFeedUrl() { + return this.feedUrl; } - public String getCategorylabel() { - return this.categorylabel; - } - public void setCategorylabel(String categorylabel) { - this.categorylabel = categorylabel; + public void setFeedUrl(String feedUrl) { + this.feedUrl = feedUrl; } - public String getSortid() { - return this.sortid; - } - public void setSortid(String sortid) { - this.sortid = sortid; + public String getHtmlUrl() { + return this.htmlUrl; } - public Long getFirstitemmsec() { - return this.firstitemmsec; - } - public void setFirstitemmsec(Long firstitemmsec) { - this.firstitemmsec = firstitemmsec; + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; } - public String getUrl() { - return this.url; - } - public void setUrl(String url) { - this.url = url; + public String getIconUrl() { + return this.iconUrl; } - public String getHtmlurl() { - return this.htmlurl; - } - public void setHtmlurl(String htmlurl) { - this.htmlurl = htmlurl; + public void setIconUrl(String iconUrl) { + this.iconUrl = iconUrl; } - public String getIconurl() { - return this.iconurl; + // 0->rss, 1->readability, 2->link + public int getDisplayMode() { + return this.displayMode; } - public void setIconurl(String iconurl) { - this.iconurl = iconurl; - } - public String getOpenMode() { - return this.openMode; + public void setDisplayMode(int displayMode) { + this.displayMode = displayMode; } - public void setOpenMode(String openMode) { - this.openMode = openMode; - } - /** - * To-many relationship, resolved on first access (and after reset). - * Changes to to-many relations are not persisted, make changes to the target entity. - */ - @Generated(hash = 1173003445) - public List
getItems() { - if (items == null) { - final DaoSession daoSession = this.daoSession; - if (daoSession == null) { - throw new DaoException("Entity is detached from DAO context"); - } - ArticleDao targetDao = daoSession.getArticleDao(); - List
itemsNew = targetDao._queryFeed_Items(id); - synchronized (this) { - if (items == null) { - items = itemsNew; - } - } - } - return items; + public int getUnreadCount() { + return this.unreadCount; } - /** - * Resets a to-many relationship, making the next get call to query for a fresh result. - */ - @Generated(hash = 1727286264) - public synchronized void resetItems() { - items = null; - } - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 128553479) - public void delete() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.delete(this); + public void setUnreadCount(int unreadCount) { + this.unreadCount = unreadCount; } - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 1942392019) - public void refresh() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.refresh(this); - } - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 713229351) - public void update() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.update(this); + public int getStarCount() { + return this.starCount; } - /** - * called by internal mechanisms, do not call yourself. - */ - @Generated(hash = 364457678) - public void __setDaoSession(DaoSession daoSession) { - this.daoSession = daoSession; - myDao = daoSession != null ? daoSession.getFeedDao() : null; + + public void setStarCount(int starCount) { + this.starCount = starCount; } - public Integer getUnreadCount() { - return this.unreadCount; + + public int getAllCount() { + return this.allCount; } - public void setUnreadCount(Integer unreadCount) { - this.unreadCount = unreadCount; + + public void setAllCount(int allCount) { + this.allCount = allCount; } - public Long getNewestItemTimestampUsec() { - return this.newestItemTimestampUsec; + + public long getState() { + return this.state; } - public void setNewestItemTimestampUsec(Long newestItemTimestampUsec) { - this.newestItemTimestampUsec = newestItemTimestampUsec; + + public void setState(long state) { + this.state = state; } + } diff --git a/app/src/main/java/me/wizos/loread/db/FeedCategory.java b/app/src/main/java/me/wizos/loread/db/FeedCategory.java new file mode 100644 index 0000000..a8bfa1c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/FeedCategory.java @@ -0,0 +1,101 @@ +package me.wizos.loread.db; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import static androidx.room.ForeignKey.CASCADE; + +/** + * Created by Wizos on 2020/3/17. + */ +@Entity(primaryKeys = {"uid","categoryId","feedId"}, + indices = {@Index({"uid"}),@Index({"categoryId","uid"}),@Index({"feedId","uid"})}, + foreignKeys = { + @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "uid", onDelete = CASCADE) +// @ForeignKey(entity = Feed.class, parentColumns = {"id","uid"}, childColumns = {"feedId","uid"}, onDelete = CASCADE), +// @ForeignKey(entity = Category.class, parentColumns = {"id","uid"}, childColumns = {"categoryId","uid"}, onDelete = CASCADE) + } +) +public class FeedCategory { + @NonNull + private String uid; + @NonNull + private String feedId; + @NonNull + private String categoryId; + + public FeedCategory(@NonNull String uid, @NonNull String feedId, @NonNull String categoryId){ + this.uid = uid; + this.feedId = feedId; + this.categoryId = categoryId; + +// if( feedId.startsWith("feed/") ){ +// this.feedId = feedId; +// }else { +// this.feedId = "feed/" + feedId; +// } +// +// if( categoryId.startsWith("user/")){ +// this.categoryId = categoryId; +// }else { +// this.categoryId = "user/" + categoryId; +// } + } + + @Override + public String toString() { + return "FeedCategory{" + + "uid=" + uid + + ", categoryId='" + categoryId + '\'' + + ", feedId='" + feedId + '\'' + + '}'; + } + + @NonNull + public String getUid() { + return uid; + } + + public void setUid(@NonNull String uid) { + this.uid = uid; + } + + public String getId() { + return this.uid; + } + + + public void setId(String uid) { + this.uid = uid; + } + + + public String getCategoryId() { + return this.categoryId; + } + + + public void setCategoryId(String categoryId) { + if( !categoryId.startsWith("user/")){ + this.categoryId = "user/" + categoryId; + }else { + this.categoryId = categoryId; + } + } + + + public String getFeedId() { + return this.feedId; + } + + + public void setFeedId(String feedId) { + if( !feedId.startsWith("feed/") ){ + this.feedId = "feed/" + feedId; + }else { + this.feedId = feedId; + } + } +} diff --git a/app/src/main/java/me/wizos/loread/db/FeedCategoryDao.java b/app/src/main/java/me/wizos/loread/db/FeedCategoryDao.java new file mode 100644 index 0000000..451d88e --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/FeedCategoryDao.java @@ -0,0 +1,50 @@ +package me.wizos.loread.db; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Transaction; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface FeedCategoryDao { + @Query("SELECT * FROM feedcategory WHERE uid = :uid") + List getAll(String uid); + + @Query("SELECT * FROM feedcategory WHERE uid = :uid AND categoryId = :categoryId") + List getByCategoryId(String uid,String categoryId); + + @Query("SELECT count(*) FROM feedcategory WHERE uid = :uid AND categoryId = :categoryId") + int getCountByCategoryId(String uid,String categoryId); + + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(FeedCategory... feedCategories); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(List feedCategories); + + @Update + @Transaction + void update(FeedCategory... feedCategories); + + @Query("UPDATE feedcategory SET categoryId = :newCategoryId where uid = :uid AND categoryId = :oldCategoryId") + void updateCategoryId(String uid,String oldCategoryId, String newCategoryId); + + + @Delete + @Transaction + void delete(FeedCategory feedCategory); + + @Query("DELETE FROM feedcategory WHERE uid = (:uid) AND feedId = :feedId") + void deleteByFeedId(String uid, String feedId); + + @Query("DELETE FROM feedcategory WHERE uid = (:uid)") + void clear(String... uid); +} diff --git a/app/src/main/java/me/wizos/loread/db/FeedDao.java b/app/src/main/java/me/wizos/loread/db/FeedDao.java new file mode 100644 index 0000000..db467aa --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/FeedDao.java @@ -0,0 +1,86 @@ +package me.wizos.loread.db; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Transaction; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface FeedDao { + @Query("SELECT * FROM feed WHERE uid = :uid") + List getAll(String uid); + + @Query("SELECT * FROM feed WHERE uid = :uid") + LiveData> getAllLiveData(String uid); + + + @Query("SELECT * FROM feed WHERE uid = :uid AND id = :id LIMIT 1") + Feed getById(String uid,String id); + +// @Query("SELECT feed.* FROM feed " + +// "LEFT JOIN feedcategory ON (feed.uid = feedcategory.uid AND feed.id = feedcategory.feedId) " + +// "WHERE feed.uid = :uid " + +// "AND feedcategory.categoryId = :categoryId " + +// "ORDER BY case when feed.unreadCount > 0 then 0 else 1 end, feed.title ASC") + + @Query("SELECT * FROM feed " + + "WHERE feed.uid = :uid " + + "AND id IN ( SELECT feedid FROM feedcategory WHERE categoryId = :categoryId) " + + "ORDER BY CASE WHEN feed.unreadCount > 0 THEN 0 ELSE 1 END, feed.title COLLATE NOCASE ASC") + List getByCategoryId(String uid,String categoryId); + + @Query("SELECT id,title,unreadCount as count FROM feed " + + "WHERE feed.uid = :uid " + + "AND id IN ( SELECT feedid FROM feedcategory WHERE categoryId = :categoryId) " + + "ORDER BY CASE WHEN feed.unreadCount > 0 THEN 0 ELSE 1 END, feed.title COLLATE NOCASE ASC") + List getFeedsUnreadCountByCategoryId(String uid, String categoryId); + + @Query("SELECT id,title,starCount as count FROM feed " + + "WHERE feed.uid = :uid " + + "AND id IN ( SELECT feedid FROM feedcategory WHERE categoryId = :categoryId) " + + "ORDER BY CASE WHEN feed.starCount > 0 THEN 0 ELSE 1 END, feed.title COLLATE NOCASE ASC") + List getFeedsStarCountByCategoryId(String uid, String categoryId); + + @Query("SELECT id,title,allCount as count FROM feed " + + "WHERE feed.uid = :uid " + + "AND id IN ( SELECT feedid FROM feedcategory WHERE categoryId = :categoryId) " + + "ORDER BY CASE WHEN feed.allCount > 0 THEN 0 ELSE 1 END, feed.title COLLATE NOCASE ASC") + List getFeedsAllCountByCategoryId(String uid, String categoryId); + + + @Query("SELECT * FROM FeedView WHERE uid = :uid" ) + List getFeedsRealTimeCount(String uid); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(Feed... feeds); + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(List feeds); + + @Update + @Transaction + void update(Feed... feeds); + @Update + @Transaction + void update(List feeds); + + @Delete + @Transaction + void delete(Feed... feeds); + @Delete + @Transaction + void delete(List feeds); + + @Query("DELETE FROM feed WHERE uid = (:uid) AND id = :id") + void deleteById(String uid, String id); + + @Query("DELETE FROM feed WHERE uid = (:uid)") + void clear(String... uid); +} diff --git a/app/src/main/java/me/wizos/loread/db/FeedView.java b/app/src/main/java/me/wizos/loread/db/FeedView.java new file mode 100644 index 0000000..4021b2e --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/FeedView.java @@ -0,0 +1,18 @@ +package me.wizos.loread.db; + +import androidx.room.DatabaseView; + +import me.wizos.loread.App; + +/** + * Feed 与 Category 是 多对多关系,即一个 Feed 可以存在与多个 Category 中,Category 也可以包含多个 Feed + * Created by Wizos on 2020/3/17. + */ +@DatabaseView( + "SELECT uid,id,title,feedUrl,htmlUrl,iconUrl,displayMode,UNREAD_SUM AS unreadCount,STAR_SUM AS starCount,ALL_SUM AS allCount,state FROM FEED" + + " LEFT JOIN (SELECT uid AS article_uid, feedId, COUNT(1) AS UNREAD_SUM FROM article WHERE readStatus != " + App.STATUS_READED + " GROUP BY uid,feedId) A ON FEED.uid = A.article_uid AND FEED.id = A.feedId" + + " LEFT JOIN (SELECT uid AS article_uid, feedId, COUNT(1) AS STAR_SUM FROM article WHERE starStatus = " + App.STATUS_STARED + " GROUP BY uid,feedId) B ON FEED.uid = B.article_uid AND FEED.id = B.feedId" + + " LEFT JOIN (SELECT uid AS article_uid, feedId, COUNT(1) AS ALL_SUM FROM article GROUP BY uid,feedId) C ON FEED.uid = c.article_uid AND FEED.id = C.feedId" +) +public class FeedView extends Feed{ +} diff --git a/app/src/main/java/me/wizos/loread/db/Tag.java b/app/src/main/java/me/wizos/loread/db/Tag.java index 4128c22..1f08222 100644 --- a/app/src/main/java/me/wizos/loread/db/Tag.java +++ b/app/src/main/java/me/wizos/loread/db/Tag.java @@ -1,74 +1,36 @@ package me.wizos.loread.db; -import com.google.gson.annotations.SerializedName; - -import org.greenrobot.greendao.DaoException; -import org.greenrobot.greendao.annotation.Entity; -import org.greenrobot.greendao.annotation.Generated; -import org.greenrobot.greendao.annotation.Id; -import org.greenrobot.greendao.annotation.Index; -import org.greenrobot.greendao.annotation.JoinProperty; -import org.greenrobot.greendao.annotation.NotNull; -import org.greenrobot.greendao.annotation.OrderBy; -import org.greenrobot.greendao.annotation.ToMany; - -import java.util.List; - -import me.wizos.loread.db.dao.DaoSession; -import me.wizos.loread.db.dao.FeedDao; -import me.wizos.loread.db.dao.TagDao; +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; +import static androidx.room.ForeignKey.CASCADE; /** - * Entity mapped to table "TAG". + * Created by Wizos on 2020/5/25. */ -@Entity -public class Tag { - @Id - @NotNull - @Index - @SerializedName("id") +@Entity(primaryKeys = {"id","uid"}, + indices = {@Index({"id"}),@Index({"uid"}),@Index({"title"})}, + foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "uid", onDelete = CASCADE)) +public class Tag { + @NonNull private String id; - @NotNull - @SerializedName("sortid") - private String sortid; - + @NonNull + private String uid; private String title; - private Integer unreadCount; - private Long newestItemTimestampUsec; - - - @ToMany(joinProperties = { - @JoinProperty(name = "id", referencedName = "categoryid") - }) - @OrderBy("categoryid DESC") - private List feeds; - - /** - * Used to resolve relations - */ - @Generated(hash = 2040040024) - private transient DaoSession daoSession; - - /** - * Used for active entity operations. - */ - @Generated(hash = 2076396065) - private transient TagDao myDao; - - @Generated(hash = 316932467) - public Tag(@NotNull String id, @NotNull String sortid, String title, Integer unreadCount, - Long newestItemTimestampUsec) { - this.id = id; - this.sortid = sortid; - this.title = title; - this.unreadCount = unreadCount; - this.newestItemTimestampUsec = newestItemTimestampUsec; + + private int unreadCount; + private int starCount; + private int allCount; + + public String getUid() { + return uid; } - @Generated(hash = 1605720318) - public Tag() { + public void setUid(String uid) { + this.uid = uid; } public String getId() { @@ -79,14 +41,6 @@ public void setId(String id) { this.id = id; } - public String getSortid() { - return this.sortid; - } - - public void setSortid(String sortid) { - this.sortid = sortid; - } - public String getTitle() { return this.title; } @@ -95,95 +49,38 @@ public void setTitle(String title) { this.title = title; } - public Integer getUnreadCount() { - return this.unreadCount; + public int getUnreadCount() { + return unreadCount; } - public void setUnreadCount(Integer unreadCount) { + public void setUnreadCount(int unreadCount) { this.unreadCount = unreadCount; } - public Long getNewestItemTimestampUsec() { - return this.newestItemTimestampUsec; - } - - public void setNewestItemTimestampUsec(Long newestItemTimestampUsec) { - this.newestItemTimestampUsec = newestItemTimestampUsec; + public int getStarCount() { + return starCount; } - /** - * To-many relationship, resolved on first access (and after reset). - * Changes to to-many relations are not persisted, make changes to the target entity. - */ - @Generated(hash = 1885689304) - public List getFeeds() { - if (feeds == null) { - final DaoSession daoSession = this.daoSession; - if (daoSession == null) { - throw new DaoException("Entity is detached from DAO context"); - } - FeedDao targetDao = daoSession.getFeedDao(); - List feedsNew = targetDao._queryTag_Feeds(id); - synchronized (this) { - if (feeds == null) { - feeds = feedsNew; - } - } - } - return feeds; + public void setStarCount(int starCount) { + this.starCount = starCount; } - /** - * Resets a to-many relationship, making the next get call to query for a fresh result. - */ - @Generated(hash = 1224133853) - public synchronized void resetFeeds() { - feeds = null; + public int getAllCount() { + return allCount; } - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 128553479) - public void delete() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.delete(this); + public void setAllCount(int allCount) { + this.allCount = allCount; } - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 1942392019) - public void refresh() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.refresh(this); + public Category convert(){ + Category category = new Category(); + category.setId(id); + category.setUid(uid); + category.setTitle(title); + category.setUnreadCount(unreadCount); + category.setStarCount(starCount); + category.setAllCount(allCount); + return category; } - - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 713229351) - public void update() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.update(this); - } - - /** - * called by internal mechanisms, do not call yourself. - */ - @Generated(hash = 441429822) - public void __setDaoSession(DaoSession daoSession) { - this.daoSession = daoSession; - myDao = daoSession != null ? daoSession.getTagDao() : null; - } - } diff --git a/app/src/main/java/me/wizos/loread/db/TagDao.java b/app/src/main/java/me/wizos/loread/db/TagDao.java new file mode 100644 index 0000000..f4db6cf --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/TagDao.java @@ -0,0 +1,48 @@ +package me.wizos.loread.db; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Transaction; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface TagDao { + @Query("SELECT * FROM tag WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + List getAll(String uid); + + @Query("SELECT * FROM tag WHERE uid = :uid ORDER BY title COLLATE NOCASE ASC") + LiveData> getAllLiveData(String uid); + + + @Query("SELECT * FROM tag WHERE uid = :uid AND id = :id LIMIT 1") + Tag getById(String uid, String id); + + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(Tag... tags); + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(List tags); + + @Update + @Transaction + void update(Tag... tags); + + @Update + @Transaction + void update(List tags); + + @Delete + @Transaction + void delete(Tag... tags); + + @Query("DELETE FROM tag WHERE uid = (:uid)") + void clear(String... uid); +} diff --git a/app/src/main/java/me/wizos/loread/db/User.java b/app/src/main/java/me/wizos/loread/db/User.java new file mode 100644 index 0000000..ed51061 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/User.java @@ -0,0 +1,310 @@ +package me.wizos.loread.db; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.Index; +import androidx.room.PrimaryKey; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.bean.Token; + + +/** + * Created by Wizos on 2020/3/17. + */ +@Entity( + indices = {@Index({"id"}),@Index({"source"}),@Index({"userId"})} ) +public class User { + @NonNull + @PrimaryKey + private String id; + // 账户信息 + private String source; + private String userId; // 该用户在服务商那的id + private String userName; // 该用户在服务商那的name + private String userEmail; + private String userPassword; + + private String tokenType; + private String accessToken; + private String refreshToken; + private String auth; + private long expiresTimestamp = 0; + + // 上次操作的状态 + private String streamId = "user/" + userId + App.CATEGORY_ALL; + private String streamTitle = App.i().getString(R.string.all); + + private int streamType = App.TYPE_GROUP; + private int streamStatus = App.STATUS_ALL; + + // 个人设置偏好 + private boolean autoSync = true; + + // 自动同步的时间间隔,单位为分钟 + private int autoSyncFrequency = 30; + private boolean autoSyncOnlyWifi = false; + + private boolean downloadImgOnlyWifi = false; + private boolean openLinkBySysBrowser = false; + //是否滚动标记为已读 + private boolean markReadOnScroll = false; + // 缓存的天数 + private int cachePeriod = 7; + private boolean autoToggleTheme = true; + private int themeMode = App.THEME_DAY; + private float audioSpeed = 1.0f; + private String host; + + public void setToken(Token token) { + if (null == token) { + return; + } + accessToken = token.getAccess_token(); + refreshToken = token.getRefresh_token(); + tokenType = token.getToken_type(); + expiresTimestamp = token.getExpires_in() + (System.currentTimeMillis() / 1000); + auth = token.getAuth(); + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserEmail() { + return userEmail; + } + + public void setUserEmail(String userEmail) { + this.userEmail = userEmail; + } + + public String getUserPassword() { + return userPassword; + } + + public void setUserPassword(String userPassword) { + this.userPassword = userPassword; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getAuth() { + return auth; + } + + public void setAuth(String auth) { + this.auth = auth; + } + + public long getExpiresTimestamp() { + return expiresTimestamp; + } + + public void setExpiresTimestamp(long expiresTimestamp) { + this.expiresTimestamp = expiresTimestamp; + } + + public int getStreamType(){ + return streamType; + } + public void setStreamType(int streamType) { + this.streamType = streamType; + } + + public String getStreamId() { + return streamId; + } + public void setStreamId(String streamId) { + this.streamId = streamId; + } + + public String getStreamTitle() { + return streamTitle; + } + + public void setStreamTitle(String streamTitle) { + this.streamTitle = streamTitle; + } + + public int getStreamStatus() { + return streamStatus; + } + + public void setStreamStatus(int streamStatus) { + this.streamStatus = streamStatus; + } + + public boolean isAutoSync() { + return autoSync; + } + + public void setAutoSync(boolean autoSync) { + this.autoSync = autoSync; + } + + public int getAutoSyncFrequency() { + return autoSyncFrequency; + } + + /** + * @param autoSyncFrequency 分钟 + */ + public void setAutoSyncFrequency(int autoSyncFrequency) { + this.autoSyncFrequency = autoSyncFrequency; + } + + public boolean isAutoSyncOnlyWifi() { + return autoSyncOnlyWifi; + } + + public void setAutoSyncOnlyWifi(boolean autoSyncOnlyWifi) { + this.autoSyncOnlyWifi = autoSyncOnlyWifi; + } + + public boolean isDownloadImgOnlyWifi() { + return downloadImgOnlyWifi; + } + + public void setDownloadImgOnlyWifi(boolean downloadImgOnlyWifi) { + this.downloadImgOnlyWifi = downloadImgOnlyWifi; + } + + public boolean isOpenLinkBySysBrowser() { + return openLinkBySysBrowser; + } + + public void setOpenLinkBySysBrowser(boolean openLinkBySysBrowser) { + this.openLinkBySysBrowser = openLinkBySysBrowser; + } + + public boolean isMarkReadOnScroll() { + return markReadOnScroll; + } + + public void setMarkReadOnScroll(boolean markReadOnScroll) { + this.markReadOnScroll = markReadOnScroll; + } + + public int getCachePeriod() { + return cachePeriod; + } + + public void setCachePeriod(int cachePeriod) { + this.cachePeriod = cachePeriod; + } + + public boolean isAutoToggleTheme() { + return autoToggleTheme; + } + + public void setAutoToggleTheme(boolean autoToggleTheme) { + this.autoToggleTheme = autoToggleTheme; + } + + public int getThemeMode() { + return themeMode; + } + + public void setThemeMode(int themeMode) { + this.themeMode = themeMode; + } + + public float getAudioSpeed() { + return audioSpeed; + } + + public void setAudioSpeed(float audioSpeed) { + this.audioSpeed = audioSpeed; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", source='" + source + '\'' + + ", userId='" + userId + '\'' + + ", userName='" + userName + '\'' + + ", userEmail='" + userEmail + '\'' + + ", userPassword='" + userPassword + '\'' + + ", tokenType='" + tokenType + '\'' + + ", accessToken='" + accessToken + '\'' + + ", refreshToken='" + refreshToken + '\'' + + ", auth='" + auth + '\'' + + ", expiresTimestamp=" + expiresTimestamp + + ", streamId='" + streamId + '\'' + + ", streamTitle='" + streamTitle + '\'' + + ", streamStatus=" + streamStatus + + ", autoSync=" + autoSync + + ", autoSyncFrequency=" + autoSyncFrequency + + ", autoSyncOnlyWifi=" + autoSyncOnlyWifi + + ", downloadImgOnlyWifi=" + downloadImgOnlyWifi + + ", openLinkBySysBrowser=" + openLinkBySysBrowser + + ", markReadOnScroll=" + markReadOnScroll + + ", cachePeriod=" + cachePeriod + + ", autoToggleTheme=" + autoToggleTheme + + ", themeMode=" + themeMode + + ", audioSpeed=" + audioSpeed + + '}'; + } +} diff --git a/app/src/main/java/me/wizos/loread/db/UserDao.java b/app/src/main/java/me/wizos/loread/db/UserDao.java new file mode 100644 index 0000000..e4892ff --- /dev/null +++ b/app/src/main/java/me/wizos/loread/db/UserDao.java @@ -0,0 +1,40 @@ +package me.wizos.loread.db; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Transaction; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface UserDao { + @Query("SELECT * FROM user") + List loadAll(); + + @Query("SELECT * FROM user WHERE id = :uid LIMIT 1") + User getById(String uid); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + @Transaction + void insert(User... users); + + @Update + @Transaction + void update(User... Users); + + @Delete + @Transaction + void delete(User... users); + + @Query("DELETE FROM user WHERE id = (:uid)") + @Transaction + void delete(String... uid); + + @Query("DELETE FROM user") + @Transaction + void clear(); +} diff --git a/app/src/main/java/me/wizos/loread/db/dao/ArticleDao.java b/app/src/main/java/me/wizos/loread/db/dao/ArticleDao.java deleted file mode 100644 index bf52297..0000000 --- a/app/src/main/java/me/wizos/loread/db/dao/ArticleDao.java +++ /dev/null @@ -1,454 +0,0 @@ -package me.wizos.loread.db.dao; - -import android.database.Cursor; -import android.database.sqlite.SQLiteStatement; - -import org.greenrobot.greendao.AbstractDao; -import org.greenrobot.greendao.Property; -import org.greenrobot.greendao.database.Database; -import org.greenrobot.greendao.database.DatabaseStatement; -import org.greenrobot.greendao.internal.DaoConfig; -import org.greenrobot.greendao.query.Query; -import org.greenrobot.greendao.query.QueryBuilder; - -import java.util.List; - -import me.wizos.loread.db.Article; - -// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. - -/** - * DAO for table "ARTICLE". - */ -public class ArticleDao extends AbstractDao { - - public static final String TABLENAME = "ARTICLE"; - - /** - * Properties of entity Article.
- * Can be used for QueryBuilder and for referencing column names. - */ - public static class Properties { - public final static Property Id = new Property(0, String.class, "id", true, "ID"); - public final static Property CrawlTimeMsec = new Property(1, Long.class, "crawlTimeMsec", false, "CRAWL_TIME_MSEC"); - public final static Property TimestampUsec = new Property(2, Long.class, "timestampUsec", false, "TIMESTAMP_USEC"); - public final static Property Categories = new Property(3, String.class, "categories", false, "CATEGORIES"); - public final static Property Title = new Property(4, String.class, "title", false, "TITLE"); - public final static Property Published = new Property(5, Long.class, "published", false, "PUBLISHED"); - public final static Property Updated = new Property(6, Long.class, "updated", false, "UPDATED"); - public final static Property Starred = new Property(7, Long.class, "starred", false, "STARRED"); - public final static Property Enclosure = new Property(8, String.class, "enclosure", false, "ENCLOSURE"); - public final static Property Canonical = new Property(9, String.class, "canonical", false, "CANONICAL"); - public final static Property Alternate = new Property(10, String.class, "alternate", false, "ALTERNATE"); - public final static Property Summary = new Property(11, String.class, "summary", false, "SUMMARY"); - public final static Property Content = new Property(12, String.class, "content", false, "CONTENT"); - public final static Property Author = new Property(13, String.class, "author", false, "AUTHOR"); - public final static Property ReadStatus = new Property(14, Integer.class, "readStatus", false, "READ_STATUS"); - public final static Property StarStatus = new Property(15, Integer.class, "starStatus", false, "STAR_STATUS"); - public final static Property ReadState = new Property(16, String.class, "readState", false, "READ_STATE"); - public final static Property StarState = new Property(17, String.class, "starState", false, "STAR_STATE"); - public final static Property SaveDir = new Property(18, String.class, "saveDir", false, "SAVE_DIR"); - public final static Property ImgState = new Property(19, String.class, "imgState", false, "IMG_STATE"); - public final static Property CoverSrc = new Property(20, String.class, "coverSrc", false, "COVER_SRC"); - public final static Property OriginStreamId = new Property(21, String.class, "originStreamId", false, "ORIGIN_STREAM_ID"); - public final static Property OriginTitle = new Property(22, String.class, "originTitle", false, "ORIGIN_TITLE"); - public final static Property OriginHtmlUrl = new Property(23, String.class, "originHtmlUrl", false, "ORIGIN_HTML_URL"); - } - - private Query
feed_ItemsQuery; - - public ArticleDao(DaoConfig config) { - super(config); - } - - public ArticleDao(DaoConfig config, DaoSession daoSession) { - super(config, daoSession); - } - - /** - * Creates the underlying database table. - */ - public static void createTable(Database db, boolean ifNotExists) { - String constraint = ifNotExists ? "IF NOT EXISTS ": ""; - db.execSQL("CREATE TABLE " + constraint + "\"ARTICLE\" (" + // - "\"ID\" TEXT PRIMARY KEY NOT NULL ," + // 0: id - "\"CRAWL_TIME_MSEC\" INTEGER," + // 1: crawlTimeMsec - "\"TIMESTAMP_USEC\" INTEGER," + // 2: timestampUsec - "\"CATEGORIES\" TEXT," + // 3: categories - "\"TITLE\" TEXT," + // 4: title - "\"PUBLISHED\" INTEGER," + // 5: published - "\"UPDATED\" INTEGER," + // 6: updated - "\"STARRED\" INTEGER," + // 7: starred - "\"ENCLOSURE\" TEXT," + // 8: enclosure - "\"CANONICAL\" TEXT," + // 9: canonical - "\"ALTERNATE\" TEXT," + // 10: alternate - "\"SUMMARY\" TEXT," + // 11: summary - "\"CONTENT\" TEXT," + // 12: content - "\"AUTHOR\" TEXT," + // 13: author - "\"READ_STATUS\" INTEGER NOT NULL ," + // 14: readStatus - "\"STAR_STATUS\" INTEGER NOT NULL ," + // 15: starStatus - "\"READ_STATE\" TEXT," + // 16: readState - "\"STAR_STATE\" TEXT," + // 17: starState - "\"SAVE_DIR\" TEXT," + // 18: saveDir - "\"IMG_STATE\" TEXT," + // 19: imgState - "\"COVER_SRC\" TEXT," + // 20: coverSrc - "\"ORIGIN_STREAM_ID\" TEXT," + // 21: originStreamId - "\"ORIGIN_TITLE\" TEXT," + // 22: originTitle - "\"ORIGIN_HTML_URL\" TEXT);"); // 23: originHtmlUrl - // Add Indexes - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_ID ON \"ARTICLE\"" + - " (\"ID\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_CRAWL_TIME_MSEC ON \"ARTICLE\"" + - " (\"CRAWL_TIME_MSEC\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_TIMESTAMP_USEC ON \"ARTICLE\"" + - " (\"TIMESTAMP_USEC\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_TITLE ON \"ARTICLE\"" + - " (\"TITLE\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_PUBLISHED ON \"ARTICLE\"" + - " (\"PUBLISHED\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_UPDATED ON \"ARTICLE\"" + - " (\"UPDATED\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_READ_STATUS ON \"ARTICLE\"" + - " (\"READ_STATUS\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_STAR_STATUS ON \"ARTICLE\"" + - " (\"STAR_STATUS\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_ARTICLE_ORIGIN_STREAM_ID ON \"ARTICLE\"" + - " (\"ORIGIN_STREAM_ID\" ASC);"); - } - - /** Drops the underlying database table. */ - public static void dropTable(Database db, boolean ifExists) { - String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"ARTICLE\""; - db.execSQL(sql); - } - - @Override - protected final void bindValues(DatabaseStatement stmt, Article entity) { - stmt.clearBindings(); - stmt.bindString(1, entity.getId()); - - Long crawlTimeMsec = entity.getCrawlTimeMsec(); - if (crawlTimeMsec != null) { - stmt.bindLong(2, crawlTimeMsec); - } - - Long timestampUsec = entity.getTimestampUsec(); - if (timestampUsec != null) { - stmt.bindLong(3, timestampUsec); - } - - String categories = entity.getCategories(); - if (categories != null) { - stmt.bindString(4, categories); - } - - String title = entity.getTitle(); - if (title != null) { - stmt.bindString(5, title); - } - - Long published = entity.getPublished(); - if (published != null) { - stmt.bindLong(6, published); - } - - Long updated = entity.getUpdated(); - if (updated != null) { - stmt.bindLong(7, updated); - } - - Long starred = entity.getStarred(); - if (starred != null) { - stmt.bindLong(8, starred); - } - - String enclosure = entity.getEnclosure(); - if (enclosure != null) { - stmt.bindString(9, enclosure); - } - - String canonical = entity.getCanonical(); - if (canonical != null) { - stmt.bindString(10, canonical); - } - - String alternate = entity.getAlternate(); - if (alternate != null) { - stmt.bindString(11, alternate); - } - - String summary = entity.getSummary(); - if (summary != null) { - stmt.bindString(12, summary); - } - - String content = entity.getContent(); - if (content != null) { - stmt.bindString(13, content); - } - - String author = entity.getAuthor(); - if (author != null) { - stmt.bindString(14, author); - } - stmt.bindLong(15, entity.getReadStatus()); - stmt.bindLong(16, entity.getStarStatus()); - - String readState = entity.getReadState(); - if (readState != null) { - stmt.bindString(17, readState); - } - - String starState = entity.getStarState(); - if (starState != null) { - stmt.bindString(18, starState); - } - - String saveDir = entity.getSaveDir(); - if (saveDir != null) { - stmt.bindString(19, saveDir); - } - - String imgState = entity.getImgState(); - if (imgState != null) { - stmt.bindString(20, imgState); - } - - String coverSrc = entity.getCoverSrc(); - if (coverSrc != null) { - stmt.bindString(21, coverSrc); - } - - String originStreamId = entity.getOriginStreamId(); - if (originStreamId != null) { - stmt.bindString(22, originStreamId); - } - - String originTitle = entity.getOriginTitle(); - if (originTitle != null) { - stmt.bindString(23, originTitle); - } - - String originHtmlUrl = entity.getOriginHtmlUrl(); - if (originHtmlUrl != null) { - stmt.bindString(24, originHtmlUrl); - } - } - - @Override - protected final void bindValues(SQLiteStatement stmt, Article entity) { - stmt.clearBindings(); - stmt.bindString(1, entity.getId()); - - Long crawlTimeMsec = entity.getCrawlTimeMsec(); - if (crawlTimeMsec != null) { - stmt.bindLong(2, crawlTimeMsec); - } - - Long timestampUsec = entity.getTimestampUsec(); - if (timestampUsec != null) { - stmt.bindLong(3, timestampUsec); - } - - String categories = entity.getCategories(); - if (categories != null) { - stmt.bindString(4, categories); - } - - String title = entity.getTitle(); - if (title != null) { - stmt.bindString(5, title); - } - - Long published = entity.getPublished(); - if (published != null) { - stmt.bindLong(6, published); - } - - Long updated = entity.getUpdated(); - if (updated != null) { - stmt.bindLong(7, updated); - } - - Long starred = entity.getStarred(); - if (starred != null) { - stmt.bindLong(8, starred); - } - - String enclosure = entity.getEnclosure(); - if (enclosure != null) { - stmt.bindString(9, enclosure); - } - - String canonical = entity.getCanonical(); - if (canonical != null) { - stmt.bindString(10, canonical); - } - - String alternate = entity.getAlternate(); - if (alternate != null) { - stmt.bindString(11, alternate); - } - - String summary = entity.getSummary(); - if (summary != null) { - stmt.bindString(12, summary); - } - - String content = entity.getContent(); - if (content != null) { - stmt.bindString(13, content); - } - - String author = entity.getAuthor(); - if (author != null) { - stmt.bindString(14, author); - } - stmt.bindLong(15, entity.getReadStatus()); - stmt.bindLong(16, entity.getStarStatus()); - - String readState = entity.getReadState(); - if (readState != null) { - stmt.bindString(17, readState); - } - - String starState = entity.getStarState(); - if (starState != null) { - stmt.bindString(18, starState); - } - - String saveDir = entity.getSaveDir(); - if (saveDir != null) { - stmt.bindString(19, saveDir); - } - - String imgState = entity.getImgState(); - if (imgState != null) { - stmt.bindString(20, imgState); - } - - String coverSrc = entity.getCoverSrc(); - if (coverSrc != null) { - stmt.bindString(21, coverSrc); - } - - String originStreamId = entity.getOriginStreamId(); - if (originStreamId != null) { - stmt.bindString(22, originStreamId); - } - - String originTitle = entity.getOriginTitle(); - if (originTitle != null) { - stmt.bindString(23, originTitle); - } - - String originHtmlUrl = entity.getOriginHtmlUrl(); - if (originHtmlUrl != null) { - stmt.bindString(24, originHtmlUrl); - } - } - - @Override - public String readKey(Cursor cursor, int offset) { - return cursor.getString(offset + 0); - } - - @Override - public Article readEntity(Cursor cursor, int offset) { - Article entity = new Article( // - cursor.getString(offset + 0), // id - cursor.isNull(offset + 1) ? null : cursor.getLong(offset + 1), // crawlTimeMsec - cursor.isNull(offset + 2) ? null : cursor.getLong(offset + 2), // timestampUsec - cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // categories - cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // title - cursor.isNull(offset + 5) ? null : cursor.getLong(offset + 5), // published - cursor.isNull(offset + 6) ? null : cursor.getLong(offset + 6), // updated - cursor.isNull(offset + 7) ? null : cursor.getLong(offset + 7), // starred - cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8), // enclosure - cursor.isNull(offset + 9) ? null : cursor.getString(offset + 9), // canonical - cursor.isNull(offset + 10) ? null : cursor.getString(offset + 10), // alternate - cursor.isNull(offset + 11) ? null : cursor.getString(offset + 11), // summary - cursor.isNull(offset + 12) ? null : cursor.getString(offset + 12), // content - cursor.isNull(offset + 13) ? null : cursor.getString(offset + 13), // author - cursor.getInt(offset + 14), // readStatus - cursor.getInt(offset + 15), // starStatus - cursor.isNull(offset + 16) ? null : cursor.getString(offset + 16), // readState - cursor.isNull(offset + 17) ? null : cursor.getString(offset + 17), // starState - cursor.isNull(offset + 18) ? null : cursor.getString(offset + 18), // saveDir - cursor.isNull(offset + 19) ? null : cursor.getString(offset + 19), // imgState - cursor.isNull(offset + 20) ? null : cursor.getString(offset + 20), // coverSrc - cursor.isNull(offset + 21) ? null : cursor.getString(offset + 21), // originStreamId - cursor.isNull(offset + 22) ? null : cursor.getString(offset + 22), // originTitle - cursor.isNull(offset + 23) ? null : cursor.getString(offset + 23) // originHtmlUrl - ); - return entity; - } - - @Override - public void readEntity(Cursor cursor, Article entity, int offset) { - entity.setId(cursor.getString(offset + 0)); - entity.setCrawlTimeMsec(cursor.isNull(offset + 1) ? null : cursor.getLong(offset + 1)); - entity.setTimestampUsec(cursor.isNull(offset + 2) ? null : cursor.getLong(offset + 2)); - entity.setCategories(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3)); - entity.setTitle(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); - entity.setPublished(cursor.isNull(offset + 5) ? null : cursor.getLong(offset + 5)); - entity.setUpdated(cursor.isNull(offset + 6) ? null : cursor.getLong(offset + 6)); - entity.setStarred(cursor.isNull(offset + 7) ? null : cursor.getLong(offset + 7)); - entity.setEnclosure(cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8)); - entity.setCanonical(cursor.isNull(offset + 9) ? null : cursor.getString(offset + 9)); - entity.setAlternate(cursor.isNull(offset + 10) ? null : cursor.getString(offset + 10)); - entity.setSummary(cursor.isNull(offset + 11) ? null : cursor.getString(offset + 11)); - entity.setContent(cursor.isNull(offset + 12) ? null : cursor.getString(offset + 12)); - entity.setAuthor(cursor.isNull(offset + 13) ? null : cursor.getString(offset + 13)); - entity.setReadStatus(cursor.getInt(offset + 14)); - entity.setStarStatus(cursor.getInt(offset + 15)); - entity.setReadState(cursor.isNull(offset + 16) ? null : cursor.getString(offset + 16)); - entity.setStarState(cursor.isNull(offset + 17) ? null : cursor.getString(offset + 17)); - entity.setSaveDir(cursor.isNull(offset + 18) ? null : cursor.getString(offset + 18)); - entity.setImgState(cursor.isNull(offset + 19) ? null : cursor.getString(offset + 19)); - entity.setCoverSrc(cursor.isNull(offset + 20) ? null : cursor.getString(offset + 20)); - entity.setOriginStreamId(cursor.isNull(offset + 21) ? null : cursor.getString(offset + 21)); - entity.setOriginTitle(cursor.isNull(offset + 22) ? null : cursor.getString(offset + 22)); - entity.setOriginHtmlUrl(cursor.isNull(offset + 23) ? null : cursor.getString(offset + 23)); - } - - @Override - protected final String updateKeyAfterInsert(Article entity, long rowId) { - return entity.getId(); - } - - @Override - public String getKey(Article entity) { - if (entity != null) { - return entity.getId(); - } else { - return null; - } - } - - @Override - public boolean hasKey(Article entity) { - throw new UnsupportedOperationException("Unsupported for entities with a non-null key"); - } - - @Override - protected final boolean isEntityUpdateable() { - return true; - } - - /** Internal query to resolve the "items" to-many relationship of Feed. */ - public List
_queryFeed_Items(String categories) { - synchronized (this) { - if (feed_ItemsQuery == null) { - QueryBuilder
queryBuilder = queryBuilder(); - queryBuilder.where(Properties.Categories.eq(null)); - queryBuilder.orderRaw("T.'TIMESTAMP_USEC' DESC"); - feed_ItemsQuery = queryBuilder.build(); - } - } - Query
query = feed_ItemsQuery.forCurrentThread(); - query.setParameter(0, categories); - return query.list(); - } - -} diff --git a/app/src/main/java/me/wizos/loread/db/dao/DaoMaster.java b/app/src/main/java/me/wizos/loread/db/dao/DaoMaster.java deleted file mode 100644 index d729078..0000000 --- a/app/src/main/java/me/wizos/loread/db/dao/DaoMaster.java +++ /dev/null @@ -1,104 +0,0 @@ -package me.wizos.loread.db.dao; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.util.Log; - -import org.greenrobot.greendao.AbstractDaoMaster; -import org.greenrobot.greendao.database.Database; -import org.greenrobot.greendao.database.DatabaseOpenHelper; -import org.greenrobot.greendao.database.StandardDatabase; -import org.greenrobot.greendao.identityscope.IdentityScopeType; - - -// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. -/** - * Master of DAO (schema version 9): knows all DAOs. - */ -public class DaoMaster extends AbstractDaoMaster { - public static final int SCHEMA_VERSION = 9; - - /** - * Creates underlying database table using DAOs. - */ - public static void createAllTables(Database db, boolean ifNotExists) { - ArticleDao.createTable(db, ifNotExists); - FeedDao.createTable(db, ifNotExists); - TagDao.createTable(db, ifNotExists); - } - - /** Drops underlying database table using DAOs. */ - public static void dropAllTables(Database db, boolean ifExists) { - ArticleDao.dropTable(db, ifExists); - FeedDao.dropTable(db, ifExists); - TagDao.dropTable(db, ifExists); - } - - /** - * WARNING: Drops all table on Upgrade! Use only during development. - * Convenience method using a {@link DevOpenHelper}. - */ - public static DaoSession newDevSession(Context context, String name) { - Database db = new DevOpenHelper(context, name).getWritableDb(); - DaoMaster daoMaster = new DaoMaster(db); - return daoMaster.newSession(); - } - - public DaoMaster(SQLiteDatabase db) { - this(new StandardDatabase(db)); - } - - public DaoMaster(Database db) { - super(db, SCHEMA_VERSION); - registerDaoClass(ArticleDao.class); - registerDaoClass(FeedDao.class); - registerDaoClass(TagDao.class); - } - - public DaoSession newSession() { - return new DaoSession(db, IdentityScopeType.Session, daoConfigMap); - } - - public DaoSession newSession(IdentityScopeType type) { - return new DaoSession(db, type, daoConfigMap); - } - - /** - * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} - - */ - public static abstract class OpenHelper extends DatabaseOpenHelper { - public OpenHelper(Context context, String name) { - super(context, name, SCHEMA_VERSION); - } - - public OpenHelper(Context context, String name, CursorFactory factory) { - super(context, name, factory, SCHEMA_VERSION); - } - - @Override - public void onCreate(Database db) { - Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION); - createAllTables(db, false); - } - } - - /** WARNING: Drops all table on Upgrade! Use only during development. */ - public static class DevOpenHelper extends OpenHelper { - public DevOpenHelper(Context context, String name) { - super(context, name); - } - - public DevOpenHelper(Context context, String name, CursorFactory factory) { - super(context, name, factory); - } - - @Override - public void onUpgrade(Database db, int oldVersion, int newVersion) { - Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); - dropAllTables(db, true); - onCreate(db); - } - } - -} diff --git a/app/src/main/java/me/wizos/loread/db/dao/DaoSession.java b/app/src/main/java/me/wizos/loread/db/dao/DaoSession.java deleted file mode 100644 index bb782e0..0000000 --- a/app/src/main/java/me/wizos/loread/db/dao/DaoSession.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.wizos.loread.db.dao; - -import org.greenrobot.greendao.AbstractDao; -import org.greenrobot.greendao.AbstractDaoSession; -import org.greenrobot.greendao.database.Database; -import org.greenrobot.greendao.identityscope.IdentityScopeType; -import org.greenrobot.greendao.internal.DaoConfig; - -import java.util.Map; - -import me.wizos.loread.db.Article; -import me.wizos.loread.db.Feed; -import me.wizos.loread.db.Tag; - -// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. - -/** - * {@inheritDoc} - * - * @see org.greenrobot.greendao.AbstractDaoSession - */ -public class DaoSession extends AbstractDaoSession { - - private final DaoConfig articleDaoConfig; - private final DaoConfig feedDaoConfig; - private final DaoConfig tagDaoConfig; - - private final ArticleDao articleDao; - private final FeedDao feedDao; - private final TagDao tagDao; - - public DaoSession(Database db, IdentityScopeType type, Map>, DaoConfig> - daoConfigMap) { - super(db); - - articleDaoConfig = daoConfigMap.get(ArticleDao.class).clone(); - articleDaoConfig.initIdentityScope(type); - - feedDaoConfig = daoConfigMap.get(FeedDao.class).clone(); - feedDaoConfig.initIdentityScope(type); - - tagDaoConfig = daoConfigMap.get(TagDao.class).clone(); - tagDaoConfig.initIdentityScope(type); - - articleDao = new ArticleDao(articleDaoConfig, this); - feedDao = new FeedDao(feedDaoConfig, this); - tagDao = new TagDao(tagDaoConfig, this); - - registerDao(Article.class, articleDao); - registerDao(Feed.class, feedDao); - registerDao(Tag.class, tagDao); - } - - public void clear() { - articleDaoConfig.clearIdentityScope(); - feedDaoConfig.clearIdentityScope(); - tagDaoConfig.clearIdentityScope(); - } - - public ArticleDao getArticleDao() { - return articleDao; - } - - public FeedDao getFeedDao() { - return feedDao; - } - - public TagDao getTagDao() { - return tagDao; - } - -} diff --git a/app/src/main/java/me/wizos/loread/db/dao/FeedDao.java b/app/src/main/java/me/wizos/loread/db/dao/FeedDao.java deleted file mode 100644 index fce1385..0000000 --- a/app/src/main/java/me/wizos/loread/db/dao/FeedDao.java +++ /dev/null @@ -1,289 +0,0 @@ -package me.wizos.loread.db.dao; - -import android.database.Cursor; -import android.database.sqlite.SQLiteStatement; - -import org.greenrobot.greendao.AbstractDao; -import org.greenrobot.greendao.Property; -import org.greenrobot.greendao.database.Database; -import org.greenrobot.greendao.database.DatabaseStatement; -import org.greenrobot.greendao.internal.DaoConfig; -import org.greenrobot.greendao.query.Query; -import org.greenrobot.greendao.query.QueryBuilder; - -import java.util.List; - -import me.wizos.loread.db.Feed; - -// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. - -/** - * DAO for table "FEED". - */ -public class FeedDao extends AbstractDao { - - public static final String TABLENAME = "FEED"; - - /** - * Properties of entity Feed.
- * Can be used for QueryBuilder and for referencing column names. - */ - public static class Properties { - public final static Property Id = new Property(0, String.class, "id", true, "ID"); - public final static Property Title = new Property(1, String.class, "title", false, "TITLE"); - public final static Property Categoryid = new Property(2, String.class, "categoryid", false, "CATEGORYID"); - public final static Property Categorylabel = new Property(3, String.class, "categorylabel", false, "CATEGORYLABEL"); - public final static Property Sortid = new Property(4, String.class, "sortid", false, "SORTID"); - public final static Property Firstitemmsec = new Property(5, Long.class, "firstitemmsec", false, "FIRSTITEMMSEC"); - public final static Property Url = new Property(6, String.class, "url", false, "URL"); - public final static Property Htmlurl = new Property(7, String.class, "htmlurl", false, "HTMLURL"); - public final static Property Iconurl = new Property(8, String.class, "iconurl", false, "ICONURL"); - public final static Property OpenMode = new Property(9, String.class, "openMode", false, "OPEN_MODE"); - public final static Property UnreadCount = new Property(10, Integer.class, "unreadCount", false, "UNREAD_COUNT"); - public final static Property NewestItemTimestampUsec = new Property(11, Long.class, "newestItemTimestampUsec", false, "NEWEST_ITEM_TIMESTAMP_USEC"); - } - - private DaoSession daoSession; - - private Query tag_FeedsQuery; - - public FeedDao(DaoConfig config) { - super(config); - } - - public FeedDao(DaoConfig config, DaoSession daoSession) { - super(config, daoSession); - this.daoSession = daoSession; - } - - /** - * Creates the underlying database table. - */ - public static void createTable(Database db, boolean ifNotExists) { - String constraint = ifNotExists ? "IF NOT EXISTS ": ""; - db.execSQL("CREATE TABLE " + constraint + "\"FEED\" (" + // - "\"ID\" TEXT PRIMARY KEY NOT NULL ," + // 0: id - "\"TITLE\" TEXT NOT NULL ," + // 1: title - "\"CATEGORYID\" TEXT," + // 2: categoryid - "\"CATEGORYLABEL\" TEXT," + // 3: categorylabel - "\"SORTID\" TEXT," + // 4: sortid - "\"FIRSTITEMMSEC\" INTEGER," + // 5: firstitemmsec - "\"URL\" TEXT," + // 6: url - "\"HTMLURL\" TEXT," + // 7: htmlurl - "\"ICONURL\" TEXT," + // 8: iconurl - "\"OPEN_MODE\" TEXT," + // 9: openMode - "\"UNREAD_COUNT\" INTEGER," + // 10: unreadCount - "\"NEWEST_ITEM_TIMESTAMP_USEC\" INTEGER);"); // 11: newestItemTimestampUsec - // Add Indexes - db.execSQL("CREATE INDEX " + constraint + "IDX_FEED_ID ON \"FEED\"" + - " (\"ID\" ASC);"); - db.execSQL("CREATE INDEX " + constraint + "IDX_FEED_CATEGORYID ON \"FEED\"" + - " (\"CATEGORYID\" ASC);"); - } - - /** Drops the underlying database table. */ - public static void dropTable(Database db, boolean ifExists) { - String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"FEED\""; - db.execSQL(sql); - } - - @Override - protected final void bindValues(DatabaseStatement stmt, Feed entity) { - stmt.clearBindings(); - stmt.bindString(1, entity.getId()); - stmt.bindString(2, entity.getTitle()); - - String categoryid = entity.getCategoryid(); - if (categoryid != null) { - stmt.bindString(3, categoryid); - } - - String categorylabel = entity.getCategorylabel(); - if (categorylabel != null) { - stmt.bindString(4, categorylabel); - } - - String sortid = entity.getSortid(); - if (sortid != null) { - stmt.bindString(5, sortid); - } - - Long firstitemmsec = entity.getFirstitemmsec(); - if (firstitemmsec != null) { - stmt.bindLong(6, firstitemmsec); - } - - String url = entity.getUrl(); - if (url != null) { - stmt.bindString(7, url); - } - - String htmlurl = entity.getHtmlurl(); - if (htmlurl != null) { - stmt.bindString(8, htmlurl); - } - - String iconurl = entity.getIconurl(); - if (iconurl != null) { - stmt.bindString(9, iconurl); - } - - String openMode = entity.getOpenMode(); - if (openMode != null) { - stmt.bindString(10, openMode); - } - - Integer unreadCount = entity.getUnreadCount(); - if (unreadCount != null) { - stmt.bindLong(11, unreadCount); - } - - Long newestItemTimestampUsec = entity.getNewestItemTimestampUsec(); - if (newestItemTimestampUsec != null) { - stmt.bindLong(12, newestItemTimestampUsec); - } - } - - @Override - protected final void bindValues(SQLiteStatement stmt, Feed entity) { - stmt.clearBindings(); - stmt.bindString(1, entity.getId()); - stmt.bindString(2, entity.getTitle()); - - String categoryid = entity.getCategoryid(); - if (categoryid != null) { - stmt.bindString(3, categoryid); - } - - String categorylabel = entity.getCategorylabel(); - if (categorylabel != null) { - stmt.bindString(4, categorylabel); - } - - String sortid = entity.getSortid(); - if (sortid != null) { - stmt.bindString(5, sortid); - } - - Long firstitemmsec = entity.getFirstitemmsec(); - if (firstitemmsec != null) { - stmt.bindLong(6, firstitemmsec); - } - - String url = entity.getUrl(); - if (url != null) { - stmt.bindString(7, url); - } - - String htmlurl = entity.getHtmlurl(); - if (htmlurl != null) { - stmt.bindString(8, htmlurl); - } - - String iconurl = entity.getIconurl(); - if (iconurl != null) { - stmt.bindString(9, iconurl); - } - - String openMode = entity.getOpenMode(); - if (openMode != null) { - stmt.bindString(10, openMode); - } - - Integer unreadCount = entity.getUnreadCount(); - if (unreadCount != null) { - stmt.bindLong(11, unreadCount); - } - - Long newestItemTimestampUsec = entity.getNewestItemTimestampUsec(); - if (newestItemTimestampUsec != null) { - stmt.bindLong(12, newestItemTimestampUsec); - } - } - - @Override - protected final void attachEntity(Feed entity) { - super.attachEntity(entity); - entity.__setDaoSession(daoSession); - } - - @Override - public String readKey(Cursor cursor, int offset) { - return cursor.getString(offset + 0); - } - - @Override - public Feed readEntity(Cursor cursor, int offset) { - Feed entity = new Feed( // - cursor.getString(offset + 0), // id - cursor.getString(offset + 1), // title - cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // categoryid - cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // categorylabel - cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // sortid - cursor.isNull(offset + 5) ? null : cursor.getLong(offset + 5), // firstitemmsec - cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6), // url - cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7), // htmlurl - cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8), // iconurl - cursor.isNull(offset + 9) ? null : cursor.getString(offset + 9), // openMode - cursor.isNull(offset + 10) ? null : cursor.getInt(offset + 10), // unreadCount - cursor.isNull(offset + 11) ? null : cursor.getLong(offset + 11) // newestItemTimestampUsec - ); - return entity; - } - - @Override - public void readEntity(Cursor cursor, Feed entity, int offset) { - entity.setId(cursor.getString(offset + 0)); - entity.setTitle(cursor.getString(offset + 1)); - entity.setCategoryid(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); - entity.setCategorylabel(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3)); - entity.setSortid(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); - entity.setFirstitemmsec(cursor.isNull(offset + 5) ? null : cursor.getLong(offset + 5)); - entity.setUrl(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6)); - entity.setHtmlurl(cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7)); - entity.setIconurl(cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8)); - entity.setOpenMode(cursor.isNull(offset + 9) ? null : cursor.getString(offset + 9)); - entity.setUnreadCount(cursor.isNull(offset + 10) ? null : cursor.getInt(offset + 10)); - entity.setNewestItemTimestampUsec(cursor.isNull(offset + 11) ? null : cursor.getLong(offset + 11)); - } - - @Override - protected final String updateKeyAfterInsert(Feed entity, long rowId) { - return entity.getId(); - } - - @Override - public String getKey(Feed entity) { - if (entity != null) { - return entity.getId(); - } else { - return null; - } - } - - @Override - public boolean hasKey(Feed entity) { - throw new UnsupportedOperationException("Unsupported for entities with a non-null key"); - } - - @Override - protected final boolean isEntityUpdateable() { - return true; - } - - /** Internal query to resolve the "feeds" to-many relationship of Tag. */ - public List _queryTag_Feeds(String categoryid) { - synchronized (this) { - if (tag_FeedsQuery == null) { - QueryBuilder queryBuilder = queryBuilder(); - queryBuilder.where(Properties.Categoryid.eq(null)); - queryBuilder.orderRaw("T.'CATEGORYID' DESC"); - tag_FeedsQuery = queryBuilder.build(); - } - } - Query query = tag_FeedsQuery.forCurrentThread(); - query.setParameter(0, categoryid); - return query.list(); - } - -} diff --git a/app/src/main/java/me/wizos/loread/db/dao/SQLiteOpenHelperS.java b/app/src/main/java/me/wizos/loread/db/dao/SQLiteOpenHelperS.java deleted file mode 100644 index 60c47cb..0000000 --- a/app/src/main/java/me/wizos/loread/db/dao/SQLiteOpenHelperS.java +++ /dev/null @@ -1,211 +0,0 @@ -package me.wizos.loread.db.dao; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; - -import com.github.yuweiguocn.library.greendao.MigrationHelper; -import com.socks.library.KLog; - -import java.io.File; - -import me.wizos.loread.App; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.net.Api; -import me.wizos.loread.utils.FileUtil; - -/** - * @author Wizos on 2016/3/15. - */ - -public class SQLiteOpenHelperS extends DaoMaster.OpenHelper { - public SQLiteOpenHelperS(Context context, String name, SQLiteDatabase.CursorFactory factory) { - super(context, name, factory); - } - - - @Override - public void onOpen(SQLiteDatabase db) { - super.onOpen(db); - if (!db.isReadOnly()) { - // 启用外键约束 - db.setForeignKeyConstraintsEnabled(true); - } - createViewsAndTriggers(db); - } - - /** - * 创建视图与触发器 - * - * @param db - */ - private void createViewsAndTriggers(SQLiteDatabase db) { - String CREATE_COUNT_VIEW = - "CREATE TEMP VIEW IF NOT EXISTS FEED_UNREAD_COUNT" + - " AS SELECT ID,TITLE,CATEGORYID,CATEGORYLABEL,SORTID,FIRSTITEMMSEC,URL,HTMLURL,ICONURL,OPEN_MODE,NEWEST_ITEM_TIMESTAMP_USEC,UNREADCOUNT" + - " FROM FEED" + - " LEFT JOIN (SELECT COUNT(1) AS UNREADCOUNT, ORIGIN_STREAM_ID" + - " FROM ARTICLE WHERE READ_STATUS != " + Api.READED + " GROUP BY ORIGIN_STREAM_ID)" + - " ON ID = ORIGIN_STREAM_ID"; - -// String CREATE_COUNT_VIEW = -// "CREATE TEMP VIEW IF NOT EXISTS FEED_COUNT" + -// " AS SELECT ID,TITLE,CATEGORYID,CATEGORYLABEL,SORTID,FIRSTITEMMSEC,URL,HTMLURL,ICONURL,OPEN_MODE,NEWEST_ITEM_TIMESTAMP_USEC,UNREADCOUNT,STAREDCOUNT,ALLCOUNT" + -// " FROM FEED" + -// " LEFT JOIN (SELECT COUNT(1) AS UNREADCOUNT,ORIGIN_STREAM_ID FROM ARTICLE WHERE READ_STATUS != " + Api.READED + " GROUP BY ORIGIN_STREAM_ID) A" + -// " ON ID = A.ORIGIN_STREAM_ID" + -// " LEFT JOIN (SELECT COUNT(1) AS STAREDCOUNT,ORIGIN_STREAM_ID FROM ARTICLE WHERE STAR_STATUS == " + Api.STARED + " GROUP BY ORIGIN_STREAM_ID) B" + -// " ON ID = B.ORIGIN_STREAM_ID" + -// " LEFT JOIN (SELECT COUNT(1) AS ALLCOUNT,ORIGIN_STREAM_ID FROM ARTICLE GROUP BY ORIGIN_STREAM_ID) C" + -// " ON ID = C.ORIGIN_STREAM_ID"; - db.execSQL(CREATE_COUNT_VIEW); - - - String createTagUnreadCountView = - "CREATE TEMP VIEW IF NOT EXISTS TAG_UNREAD_COUNT" + - " AS SELECT ID,TITLE,SORTID,NEWEST_ITEM_TIMESTAMP_USEC,UNREADCOUNT" + - " FROM TAG" + - " LEFT JOIN (SELECT CATEGORYID,SUM(UNREAD_COUNT) AS UNREADCOUNT FROM FEED GROUP BY CATEGORYID ) ON ID = CATEGORYID"; - - KLog.e("创建临时视图:" + createTagUnreadCountView); - db.execSQL(createTagUnreadCountView); - -// String MARK_READ_FEED = -// " CREATE TEMP TRIGGER IF NOT EXISTS MARK_READ_FEED" + -// " AFTER UPDATE OF READ_STATUS" + -// " ON ARTICLE" + -// " WHEN (old.READ_STATUS = 1 AND new.READ_STATUS = 2)" + -// " BEGIN" + -// " UPDATE FEED" + -// " SET UNREAD_COUNT = UNREAD_COUNT - 1" + -// " WHERE ID IS old.ORIGIN_STREAM_ID;" + -// " END"; -// String MARK_READ_TAG = -// " CREATE TEMP TRIGGER IF NOT EXISTS MARK_READ_TAG" + -// " AFTER UPDATE OF UNREAD_COUNT" + -// " ON FEED" + -// " WHEN (new.UNREAD_COUNT = old.UNREAD_COUNT - 1)" + -// " BEGIN" + -// " UPDATE TAG" + -// " SET UNREAD_COUNT = UNREAD_COUNT - 1" + -// " WHERE ID IS old.CATEGORYID;" + -// " END"; -// -// String MARK_UNREAD_FEED = -// " CREATE TEMP TRIGGER IF NOT EXISTS MARK_UNREAD_FEED" + -// " AFTER UPDATE OF READ_STATUS" + -// " ON ARTICLE" + -// " WHEN (old.READ_STATUS = 2 OR old.READ_STATUS = 1) AND new.READ_STATUS = 3" + -// " BEGIN" + -// " UPDATE FEED" + -// " SET UNREAD_COUNT = UNREAD_COUNT + 1" + -// " WHERE ID IS old.ORIGIN_STREAM_ID;" + -// " END"; -// -// String MARK_UNREAD_TAG = -// " CREATE TEMP TRIGGER IF NOT EXISTS MARK_UNREAD_TAG" + -// " AFTER UPDATE OF UNREAD_COUNT" + -// " ON FEED" + -// " WHEN (new.UNREAD_COUNT = old.UNREAD_COUNT + 1)" + -// " BEGIN" + -// " UPDATE TAG" + -// " SET UNREAD_COUNT = UNREAD_COUNT + 1" + -// " WHERE ID IS old.CATEGORYID;" + -// " END"; - -// db.execSQL( MARK_READ_FEED ); -// db.execSQL( MARK_READ_TAG ); -// db.execSQL( MARK_UNREAD_FEED ); -// db.execSQL( MARK_UNREAD_TAG ); -// KLog.e("数据库,创建触发器:" ); - -// String Test = -// " CREATE TEMP TRIGGER IF NOT EXISTS MARK_READ" + -// " AFTER UPDATE OF READ_STATUS" + -// " ON ARTICLE" + -// " WHEN (old.READ_STATUS = 2 AND new.READ_STATUS = 3)" + -// " BEGIN" + -// " UPDATE FEED" + -// " SET UNREAD_COUNT = UNREAD_COUNT + 1" + -// " WHERE ID IS old.ORIGIN_STREAM_ID;" + -// " END"; - - // 当前值不等于旧值,如果新值为read,旧值可能为unread,unreading , 减一 - // 如果旧值为read,加一 - } - - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - KLog.e("升级,准备开始升级数据库" + oldVersion + " = " + newVersion); - -// //记得要修改 DaoMaster 中的数据库版本号 - switch (oldVersion) { - case 7: - MigrationHelper.migrate(db, ArticleDao.class, FeedDao.class, TagDao.class); - updateHtmlDir(); - case 8: - MigrationHelper.migrate(db, ArticleDao.class, FeedDao.class, TagDao.class); - updateData(db); - default: - break; - } - - } - - -// private MaterialDialog materialDialog; -// public void update() { -// materialDialog = new MaterialDialog.Builder(App.i()) -// .title(R.string.is_update_title) -// .content(R.string.dialog_please_wait) -// .progress(true, 0) -// .canceledOnTouchOutside(false) -// .progressIndeterminateStyle(false) -// .show(); -// materialDialog.dismiss(); -// } -private void updateData(SQLiteDatabase db) { - String sql = "UPDATE ARTICLE SET READ_STATUS = 2 WHERE READ_STATE = 'Readed';"; - db.execSQL(sql); - sql = "UPDATE ARTICLE SET READ_STATUS = 1 WHERE READ_STATE = 'UnRead';"; - db.execSQL(sql); - sql = "UPDATE ARTICLE SET READ_STATUS = 3 WHERE READ_STATE = 'UnReading';"; - db.execSQL(sql); - sql = "UPDATE ARTICLE SET STAR_STATUS = 4 WHERE STAR_STATE = 'Stared';"; - db.execSQL(sql); - sql = "UPDATE ARTICLE SET STAR_STATUS = 5 WHERE STAR_STATE = 'UnStar';"; - db.execSQL(sql); - - if (WithPref.i().getStreamState().equals("%")) { - WithPref.i().setStreamStatus(Api.ALL); - } else if (WithPref.i().getStreamState().equals("Readed")) { - WithPref.i().setStreamStatus(Api.READED); - } else if (WithPref.i().getStreamState().equals("UnRead")) { - WithPref.i().setStreamStatus(Api.UNREAD); - } else if (WithPref.i().getStreamState().equals("UnReading")) { - WithPref.i().setStreamStatus(Api.UNREADING); - } else if (WithPref.i().getStreamState().equals("Stared")) { - WithPref.i().setStreamStatus(Api.STARED); - } else if (WithPref.i().getStreamState().equals("UnStar")) { - WithPref.i().setStreamStatus(Api.UNSTAR); - } -} - - public void updateHtmlDir() { - File dir = new File(App.i().getExternalFilesDir(null) + "/cache/"); - File[] arts = dir.listFiles(); - KLog.e("文件数量:" + arts.length); - String fileTitle; - for (File sourceFile : arts) { - if (sourceFile.isDirectory()) { - fileTitle = sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf("_")); - } else { - fileTitle = sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf(".")); - } - KLog.e("文件名:" + fileTitle); - FileUtil.moveDir(sourceFile.getAbsolutePath(), App.externalFilesDir + "/cache/" + fileTitle + "/" + sourceFile.getName()); - } - } - - -} diff --git a/app/src/main/java/me/wizos/loread/db/dao/TagDao.java b/app/src/main/java/me/wizos/loread/db/dao/TagDao.java deleted file mode 100644 index 4f3b105..0000000 --- a/app/src/main/java/me/wizos/loread/db/dao/TagDao.java +++ /dev/null @@ -1,169 +0,0 @@ -package me.wizos.loread.db.dao; - -import android.database.Cursor; -import android.database.sqlite.SQLiteStatement; - -import org.greenrobot.greendao.AbstractDao; -import org.greenrobot.greendao.Property; -import org.greenrobot.greendao.database.Database; -import org.greenrobot.greendao.database.DatabaseStatement; -import org.greenrobot.greendao.internal.DaoConfig; - -import me.wizos.loread.db.Tag; - -// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. - -/** - * DAO for table "TAG". - */ -public class TagDao extends AbstractDao { - - public static final String TABLENAME = "TAG"; - - /** - * Properties of entity Tag.
- * Can be used for QueryBuilder and for referencing column names. - */ - public static class Properties { - public final static Property Id = new Property(0, String.class, "id", true, "ID"); - public final static Property Sortid = new Property(1, String.class, "sortid", false, "SORTID"); - public final static Property Title = new Property(2, String.class, "title", false, "TITLE"); - public final static Property UnreadCount = new Property(3, Integer.class, "unreadCount", false, "UNREAD_COUNT"); - public final static Property NewestItemTimestampUsec = new Property(4, Long.class, "newestItemTimestampUsec", false, "NEWEST_ITEM_TIMESTAMP_USEC"); - } - - private DaoSession daoSession; - - - public TagDao(DaoConfig config) { - super(config); - } - - public TagDao(DaoConfig config, DaoSession daoSession) { - super(config, daoSession); - this.daoSession = daoSession; - } - - /** - * Creates the underlying database table. - */ - public static void createTable(Database db, boolean ifNotExists) { - String constraint = ifNotExists ? "IF NOT EXISTS ": ""; - db.execSQL("CREATE TABLE " + constraint + "\"TAG\" (" + // - "\"ID\" TEXT PRIMARY KEY NOT NULL ," + // 0: id - "\"SORTID\" TEXT NOT NULL ," + // 1: sortid - "\"TITLE\" TEXT," + // 2: title - "\"UNREAD_COUNT\" INTEGER," + // 3: unreadCount - "\"NEWEST_ITEM_TIMESTAMP_USEC\" INTEGER);"); // 4: newestItemTimestampUsec - // Add Indexes - db.execSQL("CREATE INDEX " + constraint + "IDX_TAG_ID ON \"TAG\"" + - " (\"ID\" ASC);"); - } - - /** Drops the underlying database table. */ - public static void dropTable(Database db, boolean ifExists) { - String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"TAG\""; - db.execSQL(sql); - } - - @Override - protected final void bindValues(DatabaseStatement stmt, Tag entity) { - stmt.clearBindings(); - stmt.bindString(1, entity.getId()); - stmt.bindString(2, entity.getSortid()); - - String title = entity.getTitle(); - if (title != null) { - stmt.bindString(3, title); - } - - Integer unreadCount = entity.getUnreadCount(); - if (unreadCount != null) { - stmt.bindLong(4, unreadCount); - } - - Long newestItemTimestampUsec = entity.getNewestItemTimestampUsec(); - if (newestItemTimestampUsec != null) { - stmt.bindLong(5, newestItemTimestampUsec); - } - } - - @Override - protected final void bindValues(SQLiteStatement stmt, Tag entity) { - stmt.clearBindings(); - stmt.bindString(1, entity.getId()); - stmt.bindString(2, entity.getSortid()); - - String title = entity.getTitle(); - if (title != null) { - stmt.bindString(3, title); - } - - Integer unreadCount = entity.getUnreadCount(); - if (unreadCount != null) { - stmt.bindLong(4, unreadCount); - } - - Long newestItemTimestampUsec = entity.getNewestItemTimestampUsec(); - if (newestItemTimestampUsec != null) { - stmt.bindLong(5, newestItemTimestampUsec); - } - } - - @Override - protected final void attachEntity(Tag entity) { - super.attachEntity(entity); - entity.__setDaoSession(daoSession); - } - - @Override - public String readKey(Cursor cursor, int offset) { - return cursor.getString(offset + 0); - } - - @Override - public Tag readEntity(Cursor cursor, int offset) { - Tag entity = new Tag( // - cursor.getString(offset + 0), // id - cursor.getString(offset + 1), // sortid - cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // title - cursor.isNull(offset + 3) ? null : cursor.getInt(offset + 3), // unreadCount - cursor.isNull(offset + 4) ? null : cursor.getLong(offset + 4) // newestItemTimestampUsec - ); - return entity; - } - - @Override - public void readEntity(Cursor cursor, Tag entity, int offset) { - entity.setId(cursor.getString(offset + 0)); - entity.setSortid(cursor.getString(offset + 1)); - entity.setTitle(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); - entity.setUnreadCount(cursor.isNull(offset + 3) ? null : cursor.getInt(offset + 3)); - entity.setNewestItemTimestampUsec(cursor.isNull(offset + 4) ? null : cursor.getLong(offset + 4)); - } - - @Override - protected final String updateKeyAfterInsert(Tag entity, long rowId) { - return entity.getId(); - } - - @Override - public String getKey(Tag entity) { - if (entity != null) { - return entity.getId(); - } else { - return null; - } - } - - @Override - public boolean hasKey(Tag entity) { - throw new UnsupportedOperationException("Unsupported for entities with a non-null key"); - } - - @Override - protected final boolean isEntityUpdateable() { - return true; - } - -} diff --git a/app/src/main/java/me/wizos/loread/event/Login.java b/app/src/main/java/me/wizos/loread/event/Login.java deleted file mode 100644 index e94fa11..0000000 --- a/app/src/main/java/me/wizos/loread/event/Login.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.wizos.loread.event; - -/** - * Created by Wizos on 2018/4/26. - */ - -public class Login { - boolean success = false; - String info; - - public Login(boolean success) { - this.success = success; - } - - public Login(boolean success, String info) { - this.success = success; - this.info = info; - } - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean success) { - this.success = success; - } - - public String getInfo() { - return info; - } - - public void setInfo(String info) { - this.info = info; - } -} diff --git a/app/src/main/java/me/wizos/loread/event/Sync.java b/app/src/main/java/me/wizos/loread/event/Sync.java deleted file mode 100644 index 94e5644..0000000 --- a/app/src/main/java/me/wizos/loread/event/Sync.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.wizos.loread.event; - -/** - * Created by Wizos on 2018/4/26. - */ - -public class Sync { -// public static final String NOTICE = "me.wizos.loread.notice"; -// public static final String N_START = "me.wizos.loread.notice.on-start"; -// public static final String N_COMPLETED = "me.wizos.loread.notice.on-completed"; -// public static final String N_ERROR = "me.wizos.loread.notice.on-error"; -// public static final String N_NEWS = "me.wizos.loread.notice.news"; - - public static final int START = 0; - public static final int DOING = 1; - public static final int END = 2; - public static final int ERROR = 3; - public static final int STOP = 4; // 通知 Service 暂停同步 - - - public String notice; - public int result; - - public Sync(int success) { - this.result = success; - } - - public Sync(int success, String notice) { - this.result = success; - this.notice = notice; - } - - -} diff --git a/app/src/main/java/me/wizos/loread/contentextractor/Extractor.java b/app/src/main/java/me/wizos/loread/extractor/Extractor.java similarity index 65% rename from app/src/main/java/me/wizos/loread/contentextractor/Extractor.java rename to app/src/main/java/me/wizos/loread/extractor/Extractor.java index b7ea69c..4c565f3 100644 --- a/app/src/main/java/me/wizos/loread/contentextractor/Extractor.java +++ b/app/src/main/java/me/wizos/loread/extractor/Extractor.java @@ -15,14 +15,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -package me.wizos.loread.contentextractor; +package me.wizos.loread.extractor; -import android.text.TextUtils; -import android.util.ArrayMap; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; +import com.orhanobut.logger.Logger; import com.socks.library.KLog; import org.jsoup.nodes.Document; @@ -32,62 +27,78 @@ import org.jsoup.select.Elements; import org.jsoup.select.NodeVisitor; -import java.io.File; -import java.lang.reflect.Type; -import java.net.URL; import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import me.wizos.loread.App; -import me.wizos.loread.utils.FileUtil; - /** * Extractor could extract content,title,time from news webpage * - * @author hu + * 本工具在是在 WebCollector 的基础上参考 GeneralNewsExtractor 项目,增加“去除干扰元素”的功能,以增加提取精准度 + * @author hu,https://github.com/CrawlScript/WebCollector */ public class Extractor { - protected Document doc; - - Extractor(Document doc) { + private Document doc; + public Extractor(Document doc) { this.doc = doc; } - // protected HashMap infoMap = new HashMap(); - protected ArrayMap infoMap = new ArrayMap(); - - class CountInfo { + private HashMap bodyInfoMap = new HashMap(); + static class CountInfo { int textCount = 0; int linkTextCount = 0; int tagCount = 0; int linkTagCount = 0; double density = 0; double densitySum = 0; - double score = 0; + //double score = 0; int pCount = 0; + int punctuationCount = 0; + //double sbdi = 0; ArrayList leafList = new ArrayList(); - } // ,iframe ,br - private void clean() { - doc.select("script,noscript,style").remove(); + private void cleanBodyElement(Element body) { + body.select("script, noscript, style, link").remove(); + //body.select("*.share, *.contribution, *.copyright, *.copy-right, *.disclaimer, *.recommend, *.related, *.footer, *.comment, *.social, *.submeta").remove(); + body.select("[id*=share], [id*=contribution], [id*=copyright], [id*=copy-right], [id*=-nav], [id*=-tags], [id*=disclaimer], [id*=recommend], [id*=related], [id*=relates], [id*=archive], [id*=recent_comments], [id*=footer], [id*=social], [id*=submeta], [id*=entry-meta]").remove(); + body.select("[class*=share], [class*=contribution], [class*=copyright], [class*=copy-right], [class*=-nav], [class*=-tags], [class*=disclaimer], [class*=recommend], [class*=related], [class*=relates], [class*=archive], [class*=recent_comments], [class*=footer], [class*=social], [class*=submeta], [class*=entry-meta]").remove(); + +// // 以下为新增,主要是去除文章中的干扰元素 +// body.select("p:empty, div:empty, blockquote:empty, details:empty, details:empty, figure:empty, figcaption:empty").remove(); +// body.select("ul:empty, ol:empty, li:empty").remove(); +// body.select("table:empty, tbody:empty, th:empty, tr:empty, dt:empty, dl:empty").remove(); +// // 受 readability 启发,移除没有子元素(包括文本)的元素。 +// body.select("section:empty, h1:empty, h2:empty, h3:empty, h4:empty, h5:empty, h6:empty, ins:empty, a:empty, b:empty, string:empty, span:empty, i:empty").remove(); + + Elements elements; + boolean circulate; + do { + elements = body.select("p:empty, div:empty, blockquote:empty, details:empty, details:empty, figure:empty, figcaption:empty, ul:empty, ol:empty, li:empty, table:empty, tbody:empty, th:empty, tr:empty, dt:empty, dl:empty, section:empty, h1:empty, h2:empty, h3:empty, h4:empty, h5:empty, h6:empty, ins:empty, a:empty, b:empty, string:empty, span:empty, i:empty, section:empty, h1:empty, h2:empty, h3:empty, h4:empty, h5:empty, h6:empty, ins:empty, a:empty, b:empty, string:empty, span:empty, i:empty"); + if( elements != null && elements.size() > 0){ + //System.out.println("继续移除:" + elements.outerHtml()); + elements.remove(); + circulate = true; + }else { + circulate = false; + } + }while (circulate); } - private CountInfo computeInfo(Node node) { + private CountInfo computeInfo(Node node) { if (node instanceof Element) { Element tag = (Element) node; - CountInfo countInfo = new CountInfo(); for (Node childNode : tag.childNodes()) { CountInfo childCountInfo = computeInfo(childNode); - countInfo.textCount += childCountInfo.textCount; + countInfo.textCount += childCountInfo.textCount; // 子节点的字符串字数 + countInfo.punctuationCount += childCountInfo.punctuationCount; // 子节点的标点符号的数量 countInfo.linkTextCount += childCountInfo.linkTextCount; countInfo.tagCount += childCountInfo.tagCount; countInfo.linkTagCount += childCountInfo.linkTagCount; @@ -95,14 +106,20 @@ private CountInfo computeInfo(Node node) { countInfo.densitySum += childCountInfo.density; countInfo.pCount += childCountInfo.pCount; } - countInfo.tagCount++; + String tagName = tag.tagName(); if (tagName.equals("a")) { countInfo.linkTextCount = countInfo.textCount; countInfo.linkTagCount++; - } else if (tagName.equals("p")) { + } else if (tagName.equals("p") || tagName.equals("article")) { // || tagName.equals("img") || tagName.equals("video") || tagName.equals("audio") countInfo.pCount++; } + // 一些有样式意义的空元素不要计入打分规则,不然会有很严重的干扰,比如91论坛的文章 + else if(tagName.equals("br") || tagName.equals("hr")){ // || tagName.equals("strong") || tagName.equals("span") + return countInfo; + } + + countInfo.tagCount++; int pureLen = countInfo.textCount - countInfo.linkTextCount; int len = countInfo.tagCount - countInfo.linkTagCount; @@ -112,7 +129,23 @@ private CountInfo computeInfo(Node node) { countInfo.density = (pureLen + 0.0) / len; } - infoMap.put(tag, countInfo); + +// increase_tag_weight(tag, countInfo); +// countInfo.sbdi = calcSbdi(countInfo); +// countInfo.sbdi = (countInfo.textCount - countInfo.linkTextCount + 0.0)/(countInfo.punctuationCount + 1); +// // sbdi 不能为0,否则会导致求对数时报错。 +// if(countInfo.sbdi == 0){ +// countInfo.sbdi = 1; +// } + +// if( tag.className().equals("postmessage_5451354")){ +// System.out.println("节点: " + tag.nodeName() + "." + tag.className() +// + ", 文本长度:" + countInfo.textCount + ", 链接文本长度:" + countInfo.linkTextCount + ", tag数量" + countInfo.tagCount + ", linkTag:" + countInfo.linkTagCount +// + ", p标签:" + countInfo.pCount+ " , 文字密度:" + countInfo.density + ",文本密度总数:" + countInfo.densitySum + ", 符号密度:" +// ); +// } + + bodyInfoMap.put(tag, countInfo); return countInfo; } else if (node instanceof TextNode) { @@ -121,6 +154,7 @@ private CountInfo computeInfo(Node node) { String text = tn.text(); int len = text.length(); countInfo.textCount = len; + //countInfo.punctuationCount = countPunctuationNum(text); countInfo.leafList.add(len); return countInfo; } else { @@ -128,19 +162,45 @@ private CountInfo computeInfo(Node node) { } } - protected double computeScore(Element tag) { - CountInfo countInfo = infoMap.get(tag); - double var = Math.sqrt(computeVar(countInfo.leafList) + 1); - double score = Math.log(var) * countInfo.densitySum * Math.log(countInfo.textCount - countInfo.linkTextCount + 1) * Math.log10(countInfo.pCount + 2); - return score; + private double computeScore(Element tag) { + CountInfo countInfo = bodyInfoMap.get(tag); + double var = Math.sqrt(computeVar(countInfo.leafList) + 1); // 这一传是干嘛用的? + // * Math.log(var) * Math.log(countInfo.textCount - countInfo.linkTextCount + 1) + return countInfo.densitySum * Math.log10(countInfo.pCount + 2) * Math.log(var) * Math.log(countInfo.textCount - countInfo.linkTextCount + 1);// 最后2个中有一个是论文中计算节点文本的标准差?根据另一个库的建议,不再需要计算文本密度的标准差 +// return countInfo.densitySum * Math.log10(countInfo.pCount + 2) * Math.log(countInfo.sbdi); + } + + /** + * 计算某个节点的符号密度 + */ + private double calcSbdi(CountInfo countInfo){ + double sbdi = (countInfo.textCount - countInfo.linkTextCount + 0.0)/(countInfo.punctuationCount + 1); + // sbdi 不能为0,否则会导致求对数时报错。 + if(sbdi == 0){ + return 1; + }else { + return sbdi; + } + } + private int countPunctuationNum(String text){ + String regEx="[^`~!@#$%^&*()—\\-+=|{}':;,\\[\\].<>/?!¥…()《》{}【】‘;:”“’。, 、?]"; + Pattern p = Pattern.compile(regEx); + Matcher m = p.matcher(text);//这里把想要替换的字符串传进来 + return m.replaceAll("").trim().length(); + } + private void increase_tag_weight(Element element, CountInfo countInfo){ + if(element.hasClass("content") || element.hasClass("article") || element.hasClass("post") || element.hasClass("news")){ + countInfo.textCount = countInfo.textCount *2; + } } - protected double computeVar(ArrayList data) { + // 用这种方式而不是符号密度,貌似速度更快 + private double computeVar(ArrayList data) { if (data.size() == 0) { return 0; } if (data.size() == 1) { - return data.get(0) / 2; + return (double)data.get(0) / 2; } double sum = 0; for (Integer i : data) { @@ -161,16 +221,28 @@ protected double computeVar(ArrayList data) { * @return */ public Element getContentElement() { - clean(); - computeInfo(doc.body()); + Element bodyElement = doc.body(); + cleanBodyElement(bodyElement); + computeInfo(bodyElement); double maxScore = 0; Element content = null; - for (Map.Entry entry : infoMap.entrySet()) { +// String tt = ""; +// flag = flag.trim(); + for (Map.Entry entry : bodyInfoMap.entrySet()) { Element tag = entry.getKey(); - if (tag.tagName().equals("a") || tag == doc.body()) { + if (tag.tagName().equals("a") || tag == bodyElement) { continue; } double score = computeScore(tag); + +// tt = tag.text().trim(); +// if( tt.length() > 18){ +// tt = tt.substring(0,18); +// } +// if(tt.startsWith(flag)){ +// score = score * 1.5; +// } +// System.out.println("Tag: " + tag.nodeName() + "." + tag.className() + " , 分数:" + score + " , 文本:" + tt); if (score > maxScore) { maxScore = score; content = tag; @@ -181,7 +253,7 @@ public Element getContentElement() { // KLog.e("正文是:" + content.text()); return content; } - KLog.e("提取失败"); + Logger.e("提取失败"); return null; } @@ -214,7 +286,7 @@ public ModPage getNews() throws Exception { return modPage; } - protected String getTime(Element contentElement) throws Exception { + private String getTime(Element contentElement) throws Exception { String regex = "([1-2][0-9]{3})[^0-9]{1,5}?([0-1]?[0-9])[^0-9]{1,5}?([0-9]{1,2})[^0-9]{1,5}?([0-2]?[1-9])[^0-9]{1,5}?([0-9]{1,2})[^0-9]{1,5}?([0-9]{1,2})"; Pattern pattern = Pattern.compile(regex); Element current = contentElement; @@ -248,7 +320,7 @@ protected String getTime(Element contentElement) throws Exception { } - protected String getDate(Element contentElement) throws Exception { + private String getDate(Element contentElement) throws Exception { String regex = "([1-2][0-9]{3})[^0-9]{1,5}?([0-1]?[0-9])[^0-9]{1,5}?([0-9]{1,2})"; Pattern pattern = Pattern.compile(regex); Element current = contentElement; @@ -276,7 +348,7 @@ protected String getDate(Element contentElement) throws Exception { throw new Exception("date not found"); } - protected double strSim(String a, String b) { + private double strSim(String a, String b) { int len1 = a.length(); int len2 = b.length(); if (len1 == 0 || len2 == 0) { @@ -294,7 +366,7 @@ protected double strSim(String a, String b) { return (lcs(a, b) + 0.0) / Math.max(len1, len2); } - protected String getTitle(final Element contentElement) throws Exception { + private String getTitle(final Element contentElement) throws Exception { final ArrayList titleList = new ArrayList(); final ArrayList titleSim = new ArrayList(); final AtomicInteger contentIndex = new AtomicInteger(); @@ -355,7 +427,7 @@ public void tail(Node node, int i) { } - protected String getTitleByEditDistance(Element contentElement) throws Exception { + private String getTitleByEditDistance(Element contentElement) throws Exception { final String metaTitle = doc.title(); final ArrayList max = new ArrayList(); @@ -364,7 +436,6 @@ protected String getTitleByEditDistance(Element contentElement) throws Exception doc.body().traverse(new NodeVisitor() { @Override public void head(Node node, int i) { - if (node instanceof TextNode) { TextNode tn = (TextNode) node; String text = tn.text().trim(); @@ -391,8 +462,7 @@ public void tail(Node node, int i) { } - protected int lcs(String x, String y) { - + private int lcs(String x, String y) { int M = x.length(); int N = y.length(); if (M == 0 || N == 0) { @@ -414,7 +484,7 @@ protected int lcs(String x, String y) { } - protected int editDistance(String word1, String word2) { + private int editDistance(String word1, String word2) { int len1 = word1.length(); int len2 = word2.length(); @@ -451,113 +521,6 @@ protected int editDistance(String word1, String word2) { } -// /*输入Jsoup的Document,获取正文所在Element*/ -// public static Element getContentElementByDoc(Document doc) throws Exception { -// Extractor ce = new Extractor(doc); -// return ce.getContentElement(); -// } - -// /*输入HTML,获取正文所在Element*/ -// public static Element getContentElementByHtml(String html) throws Exception { -// Document doc = Jsoup.parse(html); -// return getContentElementByDoc(doc); -// } -// -// /*输入HTML和URL,获取正文所在Element*/ -// public static Element getContentElementByHtml(String html, String url) throws Exception { -// Document doc = Jsoup.parse(html, url); -// return getContentElementByDoc(doc); -// } - -// /*输入URL,获取正文所在Element*/ -// public static Element getContentElementByUrl(String url) throws Exception { -//// HttpRequest request = new HttpRequest(url); -//// String html = request.response().decode(); -// return getContentElementByHtml(OkGo.get(url).execute().body().string(), url); -// } - - /*输入Jsoup的Document,获取正文文本*/ - public static String getContentByDoc(String url, Document doc) { // throws Exception - Element newDoc = new Extractor(doc).getContentElement(); - if (newDoc == null) { - return ""; - } - KLog.e("自动获取规则:" + newDoc.cssSelector()); - saveSiteRule(url, newDoc.cssSelector()); - return newDoc.html(); - } - - private static void saveSiteRule(String url, String cssSelector) { - Map> ruleDict = new ArrayMap<>(); - ArrayList body = new ArrayList<>(); - body.add(cssSelector); - ruleDict.put("body", body); - Gson gson = new GsonBuilder() - .setPrettyPrinting() //对结果进行格式化,增加换行 - .disableHtmlEscaping() //避免Gson使用时将一些字符自动转换为Unicode转义字符 - .create(); - try { - URL uri = new URL(url); - File file = new File(App.externalFilesDir + "/config/sites_generate/" + uri.getHost() + ".json"); - if (file.exists()) { - return; - } - FileUtil.save(App.externalFilesDir + "/config/sites_generate/" + uri.getHost() + ".json", gson.toJson(ruleDict, ArrayMap.class)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static String getContentByRule(Document document, Map> ruleDict) { // throws Exception - Elements elements = document.getAllElements(); - Elements temp = new Elements(); - List commandValues; - if (ruleDict.containsKey("body")) { - commandValues = ruleDict.get("body"); - for (Object commandValue : commandValues) { - temp = elements.select(commandValue.toString()); - KLog.e("Body规则:" + commandValue); - if (!temp.isEmpty()) { - elements = temp; - break; - } - } - } - -// KLog.e("此时内容为A:" + elements.html()); - if (ruleDict.containsKey("strip")) { - commandValues = ruleDict.get("strip"); - for (Object commandValue : commandValues) { - elements.select(commandValue.toString()).remove(); - KLog.e("Strip规则:" + commandValue); - } - } -// KLog.e("此时内容为B:" + elements.html()); - return elements.html(); - } - - /*输入Jsoup的Document,获取正文文本*/ - public static String getContent(String url, Document doc) { // throws Exception - try { - URL uri = new URL(url); - String siteConfigContent = FileUtil.readFile("" + App.externalFilesDir + "/config/sites/" + uri.getHost() + ".json"); - - KLog.e("获取到的内容:" + siteConfigContent); - if (!TextUtils.isEmpty(siteConfigContent)) { - Type type = new TypeToken>>() { - }.getType(); - Map> ruleDict = new Gson().fromJson(new String(siteConfigContent), type); - return getContentByRule(doc, ruleDict); - } else { - return getContentByDoc(url, doc); - } - } catch (Exception e) { - e.printStackTrace(); - return ""; - } - } - - // /*输入Jsoup的Document,获取正文文本*/ // public static String getContentByDoc(Document doc,String mKeyWord) { // throws Exception // Extractor ce = new Extractor(doc,mKeyWord); @@ -590,22 +553,22 @@ public static String getContent(String url, Document doc) { // throws Exception // KLog.e("编码是2:" + doc.charset() ); // return getContentElementByDoc(doc).outerHtml(); // } -// public static String getContentHtmlByUrl(String url) throws Exception { -// Document doc = Jsoup.connect(url).get(); +// public static String getContentHtmlByUrl(String content) throws Exception { +// Document doc = Jsoup.connect(content).get(); // return getContentElementByDoc(doc).outerHtml(); // } // /*输入HTML和URL,获取正文文本*/ -// public static String getContentByHtml(String html, String url) throws Exception { -// Document doc = Jsoup.parse(html, url); +// public static String getContentByHtml(String html, String content) throws Exception { +// Document doc = Jsoup.parse(html, content); // return getContentElementByDoc(doc).text(); // } // /*输入URL,获取正文文本*/ -// public static String getContentByUrl(String url) throws Exception { -// HttpRequest request = new HttpRequest(url); +// public static String getContentByUrl(String content) throws Exception { +// HttpRequest request = new HttpRequest(content); // String html = request.response().decode(); -// return getContentByHtml(html, url); +// return getContentByHtml(html, content); // } // /*输入Jsoup的Document,获取结构化新闻信息*/ @@ -621,24 +584,24 @@ public static String getContent(String url, Document doc) { // throws Exception // } // /*输入HTML和URL,获取结构化新闻信息*/ -// public static ModPage getNewsByHtml(String html, String url) throws Exception { -// Document doc = Jsoup.parse(html, url); +// public static ModPage getNewsByHtml(String html, String content) throws Exception { +// Document doc = Jsoup.parse(html, content); // return getNewsByDoc(doc); // } // /*输入URL,获取结构化新闻信息*/ -// public static ModPage getNewsByUrl(String url) throws Exception { -//// HttpRequest request = new HttpRequest(url); +// public static ModPage getNewsByUrl(String content) throws Exception { +//// HttpRequest request = new HttpRequest(content); //// String html = request.response().decode(); -// return getNewsByHtml(OkGo.get(url).execute().body().string(), url); +// return getNewsByHtml(OkGo.get(content).execute().body().string(), content); // } // public static void main(String[] args) throws Exception { // ModPage news = Extractor.getNewsByUrl("http://www.huxiu.com/article/121959/1.html"); -// System.out.println(news.getUrl()); +// System.out.println(news.getContent()); // System.out.println(news.getTitle()); // System.out.println(news.getTime()); -// System.out.println(news.getContent()); +// System.out.println(news.getData()); // //System.out.println(news.getContentElement()); // //System.out.println(news); // } diff --git a/app/src/main/java/me/wizos/loread/extractor/ExtractorUtil.java b/app/src/main/java/me/wizos/loread/extractor/ExtractorUtil.java new file mode 100644 index 0000000..7392edf --- /dev/null +++ b/app/src/main/java/me/wizos/loread/extractor/ExtractorUtil.java @@ -0,0 +1,99 @@ +package me.wizos.loread.extractor; + +import android.net.Uri; + +import com.hjq.toast.ToastUtils; +import com.socks.library.KLog; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import javax.script.Bindings; +import javax.script.SimpleBindings; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.config.ArticleExtractConfig; +import me.wizos.loread.config.article_extract_rule.ArticleExtractRule; +import me.wizos.loread.utils.ScriptUtil; +import me.wizos.loread.utils.StringUtils; + +public class ExtractorUtil { + /*输入Jsoup的Document,获取正文文本*/ + public static String getContent(String url, Document doc) { // throws Exception + Uri uri = Uri.parse(url); + ArticleExtractRule rule; + + String content; + rule = ArticleExtractConfig.i().getRuleByDomain(uri.getHost()); + if(rule != null){ + content = getContentByRule(uri, doc, rule); + if(!StringUtils.isEmpty(content)){ + return content; + }else { + KLog.e("规则失效A"); + ToastUtils.show(App.i().getString(R.string.the_rule_of_full_text_extraction_has_expired, uri.getHost())); + } + } + + rule = ArticleExtractConfig.i().getRuleByCssSelector(doc); + if(rule != null){ + content = getContentByRule(uri, doc, rule); + if(!StringUtils.isEmpty(content)){ + return content; + }else { + KLog.e("规则失效B"); + ToastUtils.show(App.i().getString(R.string.the_rule_of_full_text_extraction_has_expired, uri.getHost())); + } + } + + //rule = ArticleExtractRuleConfig.i().getRuleByRegex(doc.outerHtml()); + //if(rule != null){ + // return getContentByRule(uri, doc, rule); + //} + + return getContentByExtractor(uri.getHost(), doc); + } + + private static String getContentByRule(Uri uri, Document doc, ArticleExtractRule rule) { + if( !StringUtils.isEmpty(rule.getDocumentTrim()) ){ + Bindings bindings = new SimpleBindings(); + bindings.put("document", doc); + bindings.put("uri", uri); + ScriptUtil.i().eval(rule.getDocumentTrim(), bindings); + } + + if( !StringUtils.isEmpty(rule.getContent()) ){ + KLog.i("提取规则", "正文:" + rule.getContent() ); + Elements contentElements = doc.select(rule.getContent()); + if (!StringUtils.isEmpty(rule.getContentStrip())) { + KLog.i("提取规则", "正文过滤:" + rule.getContentStrip() ); + // 移除不需要的内容,注意规则为空 + contentElements.select(rule.getContentStrip()).remove(); + } + + if( !StringUtils.isEmpty(rule.getContentTrim()) ){ + Bindings bindings = new SimpleBindings(); + bindings.put("content", contentElements.html()); + ScriptUtil.i().eval(rule.getContentTrim(), bindings); + KLog.i("提取规则", "正文处理:" + rule.getContentTrim() ); + return (String)bindings.get("content"); + } + return contentElements.html().trim(); + } + return null; + } + + /*输入Jsoup的Document,获取正文文本*/ + private static String getContentByExtractor(String domain, Document doc) { // throws Exception + Element newDoc = new Extractor(doc).getContentElement(); + if (newDoc == null) { + return ""; + } + KLog.i("自动获取规则:" + newDoc.cssSelector()); + String tmp1 = newDoc.cssSelector(); + ArticleExtractConfig.i().saveRuleByDomain(doc, domain,newDoc.cssSelector()); + return newDoc.html(); + } +} diff --git a/app/src/main/java/me/wizos/loread/contentextractor/ModPage.java b/app/src/main/java/me/wizos/loread/extractor/ModPage.java similarity index 98% rename from app/src/main/java/me/wizos/loread/contentextractor/ModPage.java rename to app/src/main/java/me/wizos/loread/extractor/ModPage.java index 053a7ef..2511292 100644 --- a/app/src/main/java/me/wizos/loread/contentextractor/ModPage.java +++ b/app/src/main/java/me/wizos/loread/extractor/ModPage.java @@ -15,7 +15,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -package me.wizos.loread.contentextractor; +package me.wizos.loread.extractor; import org.jsoup.nodes.Element; diff --git a/app/src/main/java/me/wizos/loread/gson/GsonEnum.java b/app/src/main/java/me/wizos/loread/gson/GsonEnum.java new file mode 100644 index 0000000..0070b0a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/gson/GsonEnum.java @@ -0,0 +1,6 @@ +package me.wizos.loread.gson; + +public interface GsonEnum { + String serialize(); + E deserialize(String jsonEnum); +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/gson/GsonEnumTypeAdapter.java b/app/src/main/java/me/wizos/loread/gson/GsonEnumTypeAdapter.java new file mode 100644 index 0000000..45f728f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/gson/GsonEnumTypeAdapter.java @@ -0,0 +1,36 @@ +package me.wizos.loread.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; + +public class GsonEnumTypeAdapter implements JsonSerializer, JsonDeserializer { + + private final GsonEnum gsonEnum; + + public GsonEnumTypeAdapter(GsonEnum gsonEnum) { + this.gsonEnum = gsonEnum; + } + + @Override + public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) { + if (null != src && src instanceof GsonEnum) { + return new JsonPrimitive(((GsonEnum) src).serialize()); + } + return null; + } + + @Override + public E deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (null != json) { + return gsonEnum.deserialize(json.getAsString()); + } + return null; + } +} diff --git a/app/src/main/java/me/wizos/loread/gson/GsonUtil.java b/app/src/main/java/me/wizos/loread/gson/GsonUtil.java new file mode 100644 index 0000000..81d9d77 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/gson/GsonUtil.java @@ -0,0 +1,34 @@ +package me.wizos.loread.gson; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; + +/** + * author Wizos + * created 2019/8/3 + */ +public class GsonUtil { + private static Gson gson = new Gson(); + + public static T fromJson(InputStream inputStream, TypeToken token) { + try { + return gson.fromJson(new InputStreamReader(inputStream, "UTF-8"), token.getType()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null; + } + + public static T fromJson(String jsonText, TypeToken token) { + try { + return gson.fromJson(jsonText, token.getType()); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/app/src/main/java/me/wizos/loread/net/Api.java b/app/src/main/java/me/wizos/loread/net/Api.java deleted file mode 100644 index 7c52f41..0000000 --- a/app/src/main/java/me/wizos/loread/net/Api.java +++ /dev/null @@ -1,118 +0,0 @@ -package me.wizos.loread.net; - -//import com.squareup.okhttp.Request; - -/** - * 基础的 Activity - * Created by Wizos on 2016/3/5. - */ -public class Api { -// static final String INOREADER_APP_ID = "1000001277"; -// static final String INOREADER_APP_KEY = "8dByWzO4AYi425yx5glICKntEY2g3uJo"; - -// public static final String HOST_OFFICIAL = "https://www.inoreader.com"; -// public static final String U_CLIENTLOGIN ="/accounts/ClientLogin"; -// public static final String U_USER_INFO ="/reader/api/0/user-info"; -// public static final String U_TAG_LIST = "/reader/api/0/tag/list"; -// public static final String U_STREAM_PREFS ="/reader/api/0/preference/stream/list"; -// public static final String U_SUSCRIPTION_LIST = "/reader/api/0/subscription/list"; // 这个不知道现在用在了什么地方 -// public static final String U_UNREAD_COUNTS ="/reader/api/0/unread-count"; -// public static final String U_ITEM_IDS = "/reader/api/0/stream/items/ids"; // 获取所有文章的id -// public static final String U_ITEM_CONTENTS = "/reader/api/0/stream/items/contents"; // 获取流的内容 -// public static final String U_EDIT_TAG ="/reader/api/0/edit-tag"; -// public static final String U_STREAM_CONTENTS ="/reader/api/0/stream/contents/"; -// public static final String U_Stream_Contents_Atom ="/reader/atom"; -// public static final String U_Stream_Contents_User ="/reader/api/0/stream/contents/user/"; - -// public static final String U_READED ="user/-/state/com.google/read"; -// public static final String U_BROADCAST ="user/-/state/com.google/broadcast"; -// public static final String U_LIKED ="user/-/state/com.google/like"; -// public static final String U_SAVED ="user/-/state/com.google/saved-web-pages"; - - public static final String U_Search = "/state/com.google/search"; - public static final String U_READING_LIST ="/state/com.google/reading-list"; - public static final String U_NO_LABEL ="/state/com.google/no-label"; - public static final String U_STARRED = "/state/com.google/starred"; - public static final String U_UNREAND ="/state/com.google/unread"; - -// public static final String MARK_READED = "me.wizos.loread.mark.readed"; -// public static final String MARK_UNREAD = "me.wizos.loread.mark.unread"; -// public static final String MARK_STARED = "me.wizos.loread.mark.stared"; -// public static final String MARK_UNSTAR = "me.wizos.loread.mark.unstar"; -// public static final String MARK_SAVED = "me.wizos.loread.mark.saved"; -// public static final String MARK_UNSAVE = "me.wizos.loread.mark.unsave"; - - - public static final String LOGIN = "me.wizos.loread.login"; - public static final String SYNC_ALL = "me.wizos.loread.sync.all"; - public final static String SYNC_STARRED = "me.wizos.loread.sync.starred"; - public final static String SYNC_HEARTBEAT = "me.wizos.loread.sync.heartbeat"; - public static final String CLEAR = "me.wizos.loread.clear"; - - public static final String NOTICE = "me.wizos.loread.notice"; - public static final String N_START = "me.wizos.loread.notice.on-start"; - public static final String N_COMPLETED = "me.wizos.loread.notice.on-completed"; - public static final String N_ERROR = "me.wizos.loread.notice.on-error"; - public static final String N_NEWS = "me.wizos.loread.notice.news"; - - public static final String EXT_TMP = ".tmp"; - public static final String Referer = "Referer"; - - /* - Streams 可以是 feeds, tags (folders) 或者是 system types. - feed/http://feeds.arstechnica.com/arstechnica/science - Feed. - user/-/label/Tech - Tag (or folder). - user/-/state/com.google/read - Read articles.已阅读文章 - user/-/state/com.google/starred - Starred articles. - user/-/state/com.google/broadcast - Broadcasted articles. - user/-/state/com.google/like - Likes articles. - user/-/state/com.google/saved-web-pages - Saved web pages. - user/-/state/com.google/reading-list.阅读列表(包括已读和未读) - */ - /** - * 从上面的API也可以知道,这些分类是很混乱的。 - * 本质上来说,Tag 或者 Feed 都是一组 Articles (最小单位) 的集合(Stream)。(Tag 是 Feed 形而上的抽离/集合) - * 而我们用户对其中某些 Article 的 Read, Star, Save, Comment, Broadcast 等操作,无意中又生成了一组集合(Stream) - * 所以为了以后的方便,我最好是抽离/包装出一套标准的 Api。 - */ - - public static final int ActivityResult_TagToMain = 1; - public static final int ActivityResult_ArtToMain = 2; - public static final int ActivityResult_SearchLocalArtsToMain = 3; - - public static final String DISPLAY_RSS = "rss"; - public static final String DISPLAY_READABILITY = "readability"; - public static final String DISPLAY_LINK = "webpage"; - - public static final int RSS = 0; - public static final int READABILITY = 1; - public static final int LINK = 2; - - public static final int MSG_DOUBLE_TAP = -1; - - - - /** - * 是否需要改变这个为 int 以方便比较呢? - */ - public static final int ALL = 0; // 状态为所有 - public static final int UNREAD = 1; // 0 未读 - public static final int READED = 2;// 1 已读 - public static final int UNREADING = 3; // 00 强制未读 - - public static final int STARED = 4; // 1 - public static final int UNSTAR = 5; // 0 - -// public static final String ART_READED = "Readed";// 1 已读 -// public static final String ART_UNREADING = "UnReading"; // 00 强制未读 -// public static final String ART_UNREAD = "UnRead"; // 0 未读 - -// public static final String ART_STARED = "Stared"; // 1 -// public static final String ART_UNSTAR = "UnStar"; // 0 -// public static final String ART_ALL = "%"; - - public static final String SAVE_DIR_CACHE = "cache"; - public static final String SAVE_DIR_BOX = "box"; - public static final String SAVE_DIR_STORE = "store"; - -} diff --git a/app/src/main/java/me/wizos/loread/net/DataApi.java b/app/src/main/java/me/wizos/loread/net/DataApi.java deleted file mode 100644 index 9c509ac..0000000 --- a/app/src/main/java/me/wizos/loread/net/DataApi.java +++ /dev/null @@ -1,774 +0,0 @@ -package me.wizos.loread.net; - -import android.support.v4.util.ArrayMap; -import android.text.TextUtils; - -import com.google.gson.Gson; -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.exception.HttpException; -import com.socks.library.KLog; -import com.tencent.bugly.crashreport.BuglyLog; -import com.tencent.bugly.crashreport.CrashReport; - -import org.greenrobot.eventbus.EventBus; -import org.jsoup.Jsoup; -import org.jsoup.select.Elements; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -import me.wizos.loread.App; -import me.wizos.loread.R; -import me.wizos.loread.bean.gson.GsSubscriptions; -import me.wizos.loread.bean.gson.GsTags; -import me.wizos.loread.bean.gson.ItemIDs; -import me.wizos.loread.bean.gson.StreamContents; -import me.wizos.loread.bean.gson.StreamPref; -import me.wizos.loread.bean.gson.StreamPrefs; -import me.wizos.loread.bean.gson.Subscriptions; -import me.wizos.loread.bean.gson.UserInfo; -import me.wizos.loread.bean.gson.itemContents.Items; -import me.wizos.loread.bean.gson.son.ItemRefs; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.db.Article; -import me.wizos.loread.db.Feed; -import me.wizos.loread.db.Tag; -import me.wizos.loread.event.Sync; -import me.wizos.loread.utils.StringUtil; -import okhttp3.FormBody; -import okhttp3.OkHttpClient; - -/** - * 该类处于 获取 -> 处理 -> 输出 数据的处理一层。主要任务是处理、保存业务数据。 - * @author Wizos on 2017/10/13. - */ - -public class DataApi { - private static DataApi dataApi; - - private DataApi() { - } - - public static DataApi i() { - if (dataApi == null) { - synchronized (DataApi.class) { - if (dataApi == null) { - dataApi = new DataApi(); - } - } - } - return dataApi; - } - - - public String fetchBootstrap() throws HttpException, IOException { - return InoApi.i().syncBootstrap(); - } - - public boolean clientLogin(String accountId, String accountPd) throws HttpException, IOException { - String info = InoApi.i().clientLogin(accountId, accountPd); - String auth = info.split("Auth=")[1].replaceAll("\n", ""); - if (TextUtils.isEmpty(auth)) { - return false; - } - InoApi.INOREADER_ATUH = "GoogleLogin auth=" + auth; - WithPref.i().setAuth(InoApi.INOREADER_ATUH); - InoApi.i().initAuthHeaders(); - return true; - } - - public void clientLogin2(String accountId, String accountPd, StringCallback cb) { - InoApi.i().clientLogin2(accountId, accountPd, cb); - } - - public long fetchUserInfo() throws HttpException, IOException { - String info = InoApi.i().fetchUserInfo(); - UserInfo userInfo = new Gson().fromJson(info, UserInfo.class); - KLog.e("此时的UID为" + userInfo.getUserId()); - WithPref.i().setUseId(userInfo.getUserId()); - return userInfo.getUserId(); -// UserID = userInfo.getUserId(); -// mUserName = userInfo.getUserName(); -// mUserProfileId = userInfo.getUserProfileId(); -// mUserEmail = userInfo.getUserEmail(); -// mIsBloggerUser = userInfo.getIsBloggerUser(); -// mSignupTimeSec = userInfo.getSignupTimeSec(); -// mIsMultiLoginEnabled = userInfo.getIsMultiLoginEnabled(); -// save("UserID" , UserID); -// save("mUserName" , mUserName); -// save("mUserEmail" , mUserEmail); - } - - public List fetchTagList() throws HttpException, IOException { - String info = InoApi.i().syncTagList(); - String[] array; - String tagTitle; - List tagListTemp = new Gson().fromJson(info, GsTags.class).getTags(); - tagListTemp.remove(0); // /state/com.google/starred - tagListTemp.remove(0); // /state/com.google/broadcast - tagListTemp.remove(0); // /state/com.google/blogger-following - -// Tag noLabelTag = new Tag(); -// noLabelTag.setTitle(App.i().getString(R.string.main_activity_title_untag)); -// noLabelTag.setId("user/" + WithPref.i().getUseId() + Api.U_NO_LABEL); -// noLabelTag.setSortid("00000001"); -// noLabelTag.__setDaoSession(App.i().getDaoSession()); -// tagListTemp.add(0,noLabelTag); - - for (Tag tag : tagListTemp) { - array = tag.getId().split("/"); - tagTitle = array[array.length - 1]; - tag.setTitle(tagTitle); - } - return tagListTemp; - } - -// private List getTagList( List tagListTemp ) { -// Tag rootTag = new Tag(); -// Tag notTag = new Tag(); -// long userID = WithPref.i().getUseId(); -// rootTag.setTitle("所有文章"); -// notTag.setTitle("未分类"); -// -// rootTag.setId("\"user/" + userID + Api.U_READING_LIST + "\""); -// rootTag.setSortid("00000000"); -// rootTag.__setDaoSession(App.i().getDaoSession()); -// -// notTag.setId("\"user/" + userID + Api.U_NO_LABEL + "\""); -// notTag.setSortid("00000001"); -// notTag.__setDaoSession(App.i().getDaoSession()); -// -// List tagList = new ArrayList<>(); -// tagList.add(rootTag); -// tagList.add(notTag); -// -// if( tagListTemp != null){ -// tagList.addAll(tagListTemp); -// } -// -// KLog.d("【listTag】 " + rootTag.toString()); -// return tagList; -// } - - public List fetchFeedList() throws HttpException, IOException { - String info = InoApi.i().syncSubList(); - KLog.i("解析SubscriptionList"); - List subscriptionses = new Gson().fromJson(info, GsSubscriptions.class).getSubscriptions(); - List feedList = new ArrayList<>(subscriptionses.size()); - Feed feed; - for (Subscriptions subscriptions : subscriptionses) { - feed = new Feed(); - feed.setId(subscriptions.getId()); - feed.setTitle(subscriptions.getTitle()); - try { - feed.setCategoryid(subscriptions.getCategories().get(0).getId()); - feed.setCategorylabel(subscriptions.getCategories().get(0).getLabel()); - } catch (Exception e) { - feed.setCategoryid("user/" + WithPref.i().getUseId() + Api.U_NO_LABEL); - // TODO: 2018/1/11 待改成引用 - feed.setCategorylabel("no-label"); - } - feed.setSortid(subscriptions.getSortid()); - feed.setFirstitemmsec(subscriptions.getFirstitemmsec()); - feed.setUrl(subscriptions.getUrl()); - feed.setHtmlurl(subscriptions.getHtmlUrl()); - feed.setIconurl(subscriptions.getIconUrl()); - feedList.add(feed); - } - return feedList; - } - - - /** - * 获取到排序规则 - * - * @param - * @throws IOException - */ - public List fetchStreamPrefs(List tagList) throws IOException { - String info = InoApi.i().syncStreamPrefs(); - StreamPrefs streamPrefs = new Gson().fromJson(info, StreamPrefs.class); - Map tagMap = new ArrayMap<>(); - for (Tag tag : tagList) { - tagMap.put(tag.getSortid(), tag); - } - KLog.e("此时的UID为" + App.UserID); - // 由 tags 的排序字符串,生成一个新的 reTags - ArrayList preferences = streamPrefs.getStreamPrefsMaps().get("user/" + App.UserID + "/state/com.google/root"); - try { - ArrayList mTagsOrderArray = getOrderArray(preferences.get(0).getValue()); - Tag tempTag; - tagList = new ArrayList<>(); - for (String sortID : mTagsOrderArray) { // 由于获取到的排序字符串中,可能会包含feed的排序id,造成从tagMap获取不到对应的tag的现象。 - tempTag = tagMap.get(sortID); - if (tempTag != null) { - tagList.add(tempTag); -// KLog.e("tag的title" + tempTag.getTitle() + tempTag.getId() ); - } - } - for (int i = 0; i < mTagsOrderArray.size(); i++) { - tempTag = tagMap.get(mTagsOrderArray.get(i)); - tempTag.setSortid(String.valueOf(i)); - tagList.add(tempTag); -// KLog.e("tag的title" + tempTag.getTitle() + tempTag.getId() ); - } - } catch (Throwable thr) { - BuglyLog.e("DataApi: ", "user/" + App.UserID + "/state/com.google/root"); - CrashReport.postCatchedException(thr); // bugly会将这个throwable上报 - orderTags(tagList); - } - return tagList; - } - - public List orderTags(List tagList) { - KLog.i("解析orderTags"); - if (tagList.size() == 0) { - return tagList; - } - - Collections.sort(tagList, new Comparator() { - @Override - public int compare(Tag o1, Tag o2) { - return o1.getTitle().compareTo(o2.getTitle()); - } - }); - for (int i = 0; i < tagList.size(); i++) { - tagList.get(i).setSortid(String.valueOf(i)); - } - - return tagList; - } - - /** - * 将记录排序规则的string转为array - * - * @param orderingString 记录排序规则的字符串 - * @return 记录排序规则的数组 - */ - private ArrayList getOrderArray(String orderingString) { - int num = orderingString.length() / 8; - ArrayList orderingArray = new ArrayList<>(num); - for (int i = 0; i < num; i++) { - orderingArray.add(orderingString.substring(i * 8, (i * 8) + 8)); - } - return orderingArray; - } - - public HashSet fetchUnreadRefs2() throws HttpException, IOException { - List itemRefs = new ArrayList<>(); - String info; - ItemIDs tempItemIDs = new ItemIDs(); - do { - info = InoApi.i().syncUnReadRefs(tempItemIDs.getContinuation()); - tempItemIDs = new Gson().fromJson(info, ItemIDs.class); - itemRefs.addAll(tempItemIDs.getItemRefs()); - } while (tempItemIDs.getContinuation() != null); - - List
localUnreadArticles = WithDB.i().getArtsUnreadNoOrder(); - Map localUnreadArticlesMap = new ArrayMap<>(localUnreadArticles.size()); - // TODO: 2017/10/15 待保存 - List
changedArticles = new ArrayList<>(); - // 筛选下来,最终要去云端获取内容的未读Refs的集合 - HashSet tempUnreadIds = new HashSet<>(itemRefs.size()); - // 数据量大的一方 - String articleId; - for (Article article : localUnreadArticles) { - articleId = article.getId(); - localUnreadArticlesMap.put(articleId, article); - } - // 数据量小的一方 - Article article; - for (ItemRefs item : itemRefs) { - articleId = item.getLongId(); - article = localUnreadArticlesMap.get(articleId); - if (article != null) { - localUnreadArticlesMap.remove(articleId); - } else { - article = WithDB.i().getArticle(articleId); - if (article != null && article.getReadStatus() == Api.READED) { - article.setReadStatus(Api.UNREAD); - changedArticles.add(article); - } else { - // 本地无,而云端有,加入要请求的未读资源 - tempUnreadIds.add(articleId); - } - } - } - for (Map.Entry entry : localUnreadArticlesMap.entrySet()) { - if (entry.getKey() != null) { - article = localUnreadArticlesMap.get(entry.getKey()); - // 本地未读设为已读 - article.setReadStatus(Api.READED); - changedArticles.add(article); - } - } - - WithDB.i().saveArticles(changedArticles); - return tempUnreadIds; - } - - - public HashSet fetchStaredRefs2() throws HttpException, IOException { - List itemRefs = new ArrayList<>(); - String info; - ItemIDs tempItemIDs = new ItemIDs(); - do { - info = InoApi.i().syncStarredRefs(tempItemIDs.getContinuation()); - tempItemIDs = new Gson().fromJson(info, ItemIDs.class); - itemRefs.addAll(tempItemIDs.getItemRefs()); - } while (tempItemIDs.getContinuation() != null); - - List
localStarredArticles = WithDB.i().getArtsStared(); - Map localStarredArticlesMap = new ArrayMap<>(localStarredArticles.size()); - List
changedArticles = new ArrayList<>(); - HashSet tempStarredIds = new HashSet<>(itemRefs.size()); - - String articleId; - // 第1步,遍历数据量大的一方A,将其比对项目放入Map中 - for (Article article : localStarredArticles) { - articleId = article.getId(); - localStarredArticlesMap.put(articleId, article); - } - - // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY - Article article; - for (ItemRefs item : itemRefs) { - articleId = item.getLongId(); - article = localStarredArticlesMap.get(articleId); - if (article != null) { - localStarredArticlesMap.remove(articleId); - } else { - article = WithDB.i().getArticle(articleId); - if (article != null) { - article.setStarStatus(Api.STARED); - changedArticles.add(article); - } else { - // 本地无,而云远端有,加入要请求的未读资源 - tempStarredIds.add(articleId); - } - } - } - - for (Map.Entry entry : localStarredArticlesMap.entrySet()) { - if (entry.getKey() != null) { - article = localStarredArticlesMap.get(entry.getKey()); - article.setStarStatus(Api.UNSTAR); - changedArticles.add(article);// 取消加星 - } - } - - WithDB.i().saveArticles(changedArticles); - return tempStarredIds; - } - - public ArrayList> splitRefs2(HashSet tempUnreadIds, HashSet tempStarredIds) { -// KLog.e("【reRefs1】云端未读" + tempUnreadIds.size() + ",云端加星" + tempStarredIds.size()); - int total = tempUnreadIds.size() > tempStarredIds.size() ? tempStarredIds.size() : tempUnreadIds.size(); - - HashSet reUnreadUnstarRefs; - HashSet reReadStarredRefs = new HashSet<>(tempStarredIds.size()); - HashSet reUnreadStarredRefs = new HashSet<>(total); - - for (String id : tempStarredIds) { - if (tempUnreadIds.contains(id)) { - tempUnreadIds.remove(id); - reUnreadStarredRefs.add(id); - } else { - reReadStarredRefs.add(id); - } - } - reUnreadUnstarRefs = tempUnreadIds; - - ArrayList> refsList = new ArrayList<>(); - refsList.add(reUnreadUnstarRefs); - refsList.add(reReadStarredRefs); - refsList.add(reUnreadStarredRefs); -// KLog.e("【reRefs2】" + reUnreadUnstarRefs.size() + "--" + reReadStarredRefs.size() + "--" + reUnreadStarredRefs.size()); - return refsList; - } - - public interface ArticleChanger { - Article change(Article article); - } - - public void fetchAllStaredStreamContent(ArticleChanger articleChanger) throws HttpException, IOException { - String continuation = null; - StreamContents streamContents; - int count = 0; - do { - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_all_stared_content, count))); - String res = InoApi.i().syncStaredStreamContents(continuation); - streamContents = new Gson().fromJson(res, StreamContents.class); - WithDB.i().saveArticles(parseItemContents(streamContents.getItems(), articleChanger)); - count = count + streamContents.getItems().size(); - continuation = streamContents.getContinuation(); - } while (streamContents.getContinuation() != null); - } - - public ArrayList
parseItemContents(String info, ArticleChanger articleChanger) { - // 如果返回 null 会与正常获取到流末端时返回 continuation = null 相同,导致调用该函数的那端误以为是正常的 continuation = null - if (info == null || info.equals("")) { - return null; - } - Gson gson = new Gson(); - StreamContents gsItemContents = gson.fromJson(info, StreamContents.class); - return parseItemContents(gsItemContents.getItems(), articleChanger); - } - - private ArrayList
parseItemContents(List itemsList, ArticleChanger articleChanger) { - // 如果返回 null 会与正常获取到流末端时返回 continuation = null 相同,导致调用该函数的那端误以为是正常的 continuation = null - if (itemsList == null || itemsList.size() <= 0) { - return null; - } - ArrayList
articleList = new ArrayList<>(itemsList.size()); - String summary = "", html = ""; - Article article; - Gson gson = new Gson(); - Elements elements; - for (Items items : itemsList) { -// if (WithDB.i().getArticleEchoes(items.getTitle(), items.getCanonical().get(0).getHref()) != 0) { -// KLog.e("重复文章:" + items.getTitle() ); -// continue; -// } -// KLog.e("文章标题:"+ items.getTitle() ); - - article = new Article(); - // 返回的字段 - article.setId(items.getId()); - article.setCrawlTimeMsec(items.getCrawlTimeMsec()); - article.setTimestampUsec(items.getTimestampUsec()); - article.setCategories(gson.toJson(items.getCategories())); - article.setTitle(items.getTitle().replace("\r", "").replace("\n", "")); - article.setPublished(items.getPublished()); - // 设置被加星的时间 - article.setStarred(items.getStarred()); - article.setCanonical(items.getCanonical().get(0).getHref()); - article.setAlternate(gson.toJson(items.getCanonical())); - article.setAuthor(items.getAuthor()); - article.setOriginStreamId(items.getOrigin().getStreamId()); - article.setOriginHtmlUrl(items.getOrigin().getHtmlUrl()); - article.setOriginTitle(items.getOrigin().getTitle()); - - html = items.getSummary().getContent(); - html = StringUtil.getOptimizedContent(html); - - // 获取视频或者音频附件 - if (items.getEnclosure() != null && items.getEnclosure().size() > 0) { - if (items.getEnclosure().get(0).getType().startsWith("audio")) { - html = html + "
"; - } else if (items.getEnclosure().get(0).getType().startsWith("video")) { - html = html + "
"; - } -// if (items.getEnclosure().get(0).getType().startsWith("image") || items.getEnclosure().get(0).getType().startsWith("parsedImg")) { -// article.setCoverSrc(items.getEnclosure().get(0).getHref()); -// } - } - - summary = StringUtil.getOptimizedSummary(html); - article.setSummary(summary); - article.setContent(html); - - // 获取第1个图片作为封面 - elements = Jsoup.parseBodyFragment(article.getContent() == null ? "" : article.getContent()).getElementsByTag("img"); - if (elements.size() > 0) { - article.setCoverSrc(elements.attr("src")); - } - - // 自己设置的字段 -// KLog.i("【增加文章】" + article.getId()); - article.setSaveDir(Api.SAVE_DIR_CACHE); - article = articleChanger.change(article); - articleList.add(article); - } - return articleList; - } - - - public void renameTag(String sourceTagId, String destTagId, StringCallback cb) { - InoApi.i().renameTag(sourceTagId, destTagId, cb); - } - - - public void addFeed(String feedId, StringCallback cb) { - // /reader/api/0/subscription/quickadd - InoApi.i().addFeed(feedId, cb); - } - - public void renameFeed(String feedId, String renamedTitle, StringCallback cb) { - InoApi.i().renameFeed(feedId, renamedTitle, cb); - } - - public void renameFeed(OkHttpClient httpClient, String feedId, String renamedTitle, StringCallback cb) { - InoApi.i().renameFeed(httpClient, feedId, renamedTitle, cb); - } - - - public void editFeed(FormBody.Builder builder, StringCallback cb) { - InoApi.i().editFeed(builder, cb); - } - - public void unsubscribeFeed(String feedId, StringCallback cb) { - InoApi.i().unsubscribeFeed(feedId, cb); - } - - public void unsubscribeFeed(OkHttpClient httpClient, String feedId, StringCallback cb) { - InoApi.i().unsubscribeFeed(httpClient, feedId, cb); - } - - public void markArticleListReaded(List articleIDs, StringCallback cb) { - InoApi.i().markArticleListReaded(articleIDs, cb); - } - - public void markArticleReaded(String articleID, StringCallback cb) { - InoApi.i().markArticleReaded(articleID, cb); - } - - public void markArticleReaded(OkHttpClient httpClient, String articleID, StringCallback cb) { - InoApi.i().markArticleReaded(httpClient, articleID, cb); - } - - public void markArticleUnread(String articleID, StringCallback cb) { - InoApi.i().markArticleUnread(articleID, cb); - } - - public void markArticleUnstar(String articleID, StringCallback cb) { - InoApi.i().markArticleUnstar(articleID, cb); - } - - public void markArticleStared(String articleID, StringCallback cb) { - InoApi.i().markArticleStared(articleID, cb); - } - - -// public void fetchAllStaredStreamContent2() throws HttpException, IOException { -// String continuation = null; -// StreamContents streamContents; -// int count = 0; -// do { -// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_all_stared_content, count))); -// String res = InoApi.i().syncStaredStreamContents(continuation); -// streamContents = new Gson().fromJson(res, StreamContents.class); -// WithDB.i().saveArticles(parseItemContents3(streamContents.getItems(), new ArticleChanger() { -// @Override -// public Article change(Article article) { -//// article.setReadState(Api.ART_READED); -//// article.setStarState(Api.ART_STARED); -// article.setReadStatus(Api.READED); -// article.setStarStatus(Api.STARED); -// return article; -// } -// })); -// count = count + streamContents.getItems().size(); -// continuation = streamContents.getContinuation(); -// } while (streamContents.getContinuation() != null); -// } - - -// private ArrayList
parseItemContents3(ArrayList itemsList, ArticleChanger articleChanger) { -// // 如果返回 null 会与正常获取到流末端时返回 continuation = null 相同,导致调用该函数的那端误以为是正常的 continuation = null -// if (itemsList == null || itemsList.size() <= 0) { -// return null; -// } -// ArrayList
articleList = new ArrayList<>(itemsList.size()); -// String summary = "", html = "", audioHtml; -// Article article; -// Gson gson = new Gson(); -// Elements elements; -// for (Items items : itemsList) { -//// if (WithDB.i().getArticleEchoes(items.getTitle(), items.getCanonical().get(0).getHref()) != 0) { -//// KLog.e("重复文章:" + items.getTitle() ); -//// continue; -//// } -//// KLog.e("文章标题:"+ items.getTitle() ); -// -// article = new Article(); -// // 返回的字段 -// article.setId(items.getId()); -// article.setCrawlTimeMsec(items.getCrawlTimeMsec()); -// article.setTimestampUsec(items.getTimestampUsec()); -// article.setCategories(gson.toJson(items.getCategories())); -// article.setTitle(items.getTitle().replace("\r", "").replace("\n", "")); -// article.setPublished(items.getPublished()); -// article.setUpdated(System.currentTimeMillis()); -// // 设置被加星的时间 -// article.setStarred(items.getStarred()); -// // items.getCanonical().get(0).getHref() -// article.setCanonical(items.getCanonical().get(0).getHref()); -// // items.getAlternate().toString() -// article.setAlternate(gson.toJson(items.getCanonical())); -// article.setAuthor(items.getAuthor()); -// article.setOriginStreamId(items.getOrigin().getStreamId()); -// article.setOriginHtmlUrl(items.getOrigin().getHtmlUrl()); -// article.setOriginTitle(items.getOrigin().getTitle()); -// -// html = items.getSummary().getContent(); -// html = StringUtil.getOptimizedContent(html); -// -// if (items.getEnclosure() != null && items.getEnclosure().size() > 0 && (items.getEnclosure().get(0).getType().startsWith("audio"))) { -// html = html + "
"; -// } -// -// summary = StringUtil.getOptimizedSummary(html); -// article.setSummary(summary); -// article.setContent(html); -// -//// if (items.getEnclosure() != null && items.getEnclosure().size() > 0 && (items.getEnclosure().get(0).getType().startsWith("image") || items.getEnclosure().get(0).getType().startsWith("parsedImg"))) { -//// article.setCoverSrc(items.getEnclosure().get(0).getHref()); -//// } -// // 获取第1个图片作为封面 -// elements = Jsoup.parseBodyFragment(article.getContent() == null ? "" : article.getContent()).getElementsByTag("img"); -// if (elements.size() > 0) { -// article.setCoverSrc(elements.attr("src")); -// } -// -// -// // 自己设置的字段 -//// KLog.i("【增加文章】" + article.getId()); -// article.setSaveDir(Api.SAVE_DIR_CACHE); -// article = articleChanger.change(article); -// articleList.add(article); -// } -//// WithDB.i().saveArticles(articleList); -// return articleList; -// } -// -// private ArrayList
parseItemContents2(String info, ArticleChanger articleChanger) { -// // 如果返回 null 会与正常获取到流末端时返回 continuation = null 相同,导致调用该函数的那端误以为是正常的 continuation = null -// if (info == null || info.equals("")) { -// return null; -// } -// Gson gson = new Gson(); -// StreamContents gsItemContents = gson.fromJson(info, StreamContents.class); -// return parseItemContents3(gsItemContents.getItems(), articleChanger); -// } - -// public void articleRemoveTag(String articleID, String tagId, StringCallback cb) { -// InoApi.i().articleRemoveTag(articleID, tagId, cb); -// } -// -// public void articleAddTag(String articleID, String tagId, StringCallback cb) { -// InoApi.i().articleAddTag(articleID, tagId, cb); -// } - - -// public List fetchUnreadCounts() throws HttpException, IOException { -// String info = InoApi.i().syncUnreadCounts(); -// List unreadCountList = new Gson().fromJson(info, GsUnreadCount.class).getUnreadcounts(); -// return unreadCountList; -// } - -// public List fetchUnreadRefs() throws HttpException, IOException { -// ItemIDs itemIDs = new ItemIDs(); -// do { -// String info = InoApi.i().syncUnReadRefs(itemIDs.getContinuation()); -// ItemIDs tempItemIDs = new Gson().fromJson(info, ItemIDs.class); -// itemIDs.addItemRefs(tempItemIDs.getItemRefs()); -// itemIDs.setContinuation(tempItemIDs.getContinuation()); -// } while (itemIDs.getContinuation() != null); -// -// -// List
localUnreadArticles = WithDB.i().getArtsUnread(); -// List
changedArticles = new ArrayList<>(); // // TODO: 2017/10/15 待保存 -// Map map = new ArrayMap<>(localUnreadArticles.size()); -// ArrayList tempUnreadRefs = new ArrayList<>(itemIDs.getItemRefs().size());// 筛选下来,最终要去云端获取内容的未读Refs的集合 -// -// // 数据量大的一方 -// String articleId; -// for (Article article : localUnreadArticles) { -// articleId = article.getId(); -// map.put(articleId, article); -//// KLog.e("文章的" + article.getId()); -// } -// // 数据量小的一方 -// Article article; -// for (ItemRefs item : itemIDs.getItemRefs()) { -// articleId = item.getLongId(); -// article = map.get(articleId); -//// KLog.e("获取到的" + item.getId() + " " + item.getLongId() ); -// if (article != null) { -// map.remove(articleId); -//// KLog.e("本地有" ); -// } else { -// article = WithDB.i().getArticle(articleId); -// if (article != null && article.getReadState().equals(Api.ART_READED)) { -//// KLog.e("本地有B" ); -// article.setReadState(Api.ART_UNREAD); -// changedArticles.add(article); -// } else { -//// KLog.e("本地无" ); -// tempUnreadRefs.add(item);// 本地无,而云远端有,加入要请求的未读资源 -// } -// } -// } -// for (Map.Entry entry : map.entrySet()) { -//// KLog.e("最终" + entry.getKey() ); -// if (entry.getKey() != null) { -// article = map.get(entry.getKey()); -// article.setReadState(Api.ART_READED); // 本地未读设为已读 -// changedArticles.add(article); -// } -// } -// -// WithDB.i().saveArticles(changedArticles); -// return tempUnreadRefs; -// } -// public List fetchStaredRefs() throws HttpException, IOException { -// ItemIDs itemIDs = new ItemIDs(); -// do { -// String info = InoApi.i().syncStarredRefs(itemIDs.getContinuation()); -// ItemIDs tempItemIDs = new Gson().fromJson(info, ItemIDs.class); -// itemIDs.addItemRefs(tempItemIDs.getItemRefs()); -// itemIDs.setContinuation(tempItemIDs.getContinuation()); -// } while (itemIDs.getContinuation() != null); -// -// List
localStarredArticles = WithDB.i().getArtsStared(); -// List
changedArticles = new ArrayList<>(); // // TODO: 2017/10/15 待保存 -// Map map = new ArrayMap<>(localStarredArticles.size()); -// ArrayList tempStarredRefs = new ArrayList<>(itemIDs.getItemRefs().size()); -// -// -// String articleId; -// // 第1步,遍历数据量大的一方A,将其比对项目放入Map中,计数为1 -// for (Article article : localStarredArticles) { -// articleId = article.getId(); // String -// map.put(articleId, article); -// } -// // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY -// Article article; -// for (ItemRefs item : itemIDs.getItemRefs()) { -//// articleId = StringUtil.toLongID(item.getLongId()); -// articleId = item.getLongId(); -// article = map.get(articleId); -// if (article != null) { -// map.remove(articleId); -// } else { -// article = WithDB.i().getArticle(articleId); -// if (article != null) { -// article.setStarState(Api.ART_STARED); -// changedArticles.add(article); -// } else { -// tempStarredRefs.add(item);// 本地无,而云远端有,加入要请求的未读资源 -// } -//// starredRefs.add(item);// 3,就剩云端的,要请求的加星资源(但是还是含有一些要请求的未读资源,和一些本地是已读的文章) -// } -// } -// -// for (Map.Entry entry : map.entrySet()) { -// if (entry.getKey() != null) { -// article = map.get(entry.getKey()); -// article.setStarState(Api.ART_UNSTAR); -// changedArticles.add(article);// 取消加星 -// } -// } -// -// WithDB.i().saveArticles(changedArticles); -// return tempStarredRefs; -// } - -} diff --git a/app/src/main/java/me/wizos/loread/net/InoApi.java b/app/src/main/java/me/wizos/loread/net/InoApi.java deleted file mode 100644 index 0e49d70..0000000 --- a/app/src/main/java/me/wizos/loread/net/InoApi.java +++ /dev/null @@ -1,328 +0,0 @@ -package me.wizos.loread.net; - -import android.text.TextUtils; - -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.exception.HttpException; -import com.lzy.okgo.model.HttpHeaders; -import com.lzy.okgo.model.HttpParams; -import com.socks.library.KLog; - -import java.io.IOException; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.wizos.loread.App; -import me.wizos.loread.data.WithPref; -import okhttp3.FormBody; -import okhttp3.OkHttpClient; - - -/** - * 本接口对接 Inoreader 服务,从他那获取数据 - * @author Wizos on 2016/3/10. - */ -public class InoApi { - // 每次获取内容的数量 - public int fetchContentCntForEach = 20; - - private static final String APP_ID = "1000001277"; - private static final String APP_KEY = "8dByWzO4AYi425yx5glICKntEY2g3uJo"; - public static String HOST = "https://www.inoreader.com"; - public static String INOREADER_ATUH = ""; - -// public static final String CLIENTLOGIN = "/accounts/ClientLogin"; -// public static final String USER_INFO = "/reader/api/0/user-info"; -// public static final String TAG_LIST = "/reader/api/0/tag/list"; -// public static final String STREAM_PREFS = "/reader/api/0/preference/stream/list"; -// public static final String SUSCRIPTION_LIST = "/reader/api/0/subscription/list"; // 这个不知道现在用在了什么地方 -// public static final String UNREAD_COUNTS = "/reader/api/0/unread-count"; - - public static final String ITEM_IDS = "/reader/api/0/stream/items/ids"; // 获取所有文章的id - public static final String ITEM_CONTENTS = "/reader/api/0/stream/items/contents"; // 获取流的内容 - public static final String EDIT_TAG = "/reader/api/0/edit-tag"; - public static final String RENAME_TAG = "/reader/api/0/rename-tag"; - public static final String EDIT_FEED = "/reader/api/0/subscription/edit"; - public static final String ADD_FEED = "/reader/api/0/subscription/quickadd"; - -// public static final String STREAM_CONTENTS = "/reader/api/0/stream/contents/"; -// public static final String Stream_Contents_Atom = "/reader/atom"; -// public static final String Stream_Contents_User = "/reader/api/0/stream/contents/user/"; -// -// public static final String READING_LIST = "/state/com.google/reading-list"; -// public static final String NO_LABEL = "/state/com.google/no-label"; -// public static final String STARRED = "/state/com.google/starred"; -// public static final String UNREAND = "/state/com.google/unread"; - - /* -Code Description -200 Request OK -400 Mandatory parameter(s) missing -401 End-user not authorized -403 You are not sending the correct AppID and/or AppSecret -404 Method not implemented -429 Daily limit reached for this zone -503 Service unavailable - */ - - - private static long mUserID; - private HttpHeaders authHeaders; - - private static InoApi inoApi; - - private InoApi() { - initAuthHeaders(); - } - - public static InoApi i() { - if (inoApi == null) { - synchronized (InoApi.class) { - if (inoApi == null) { - inoApi = new InoApi(); - mUserID = App.UserID; - } - } - } - return inoApi; - } - - public void initAuthHeaders() { - authHeaders = new HttpHeaders(); - authHeaders.put("AppId", APP_ID); - authHeaders.put("AppKey", APP_KEY); - if (!TextUtils.isEmpty(WithPref.i().getAuth())) { - authHeaders.put("Authorization", WithPref.i().getAuth()); // TEST: 这里不对 - } - } - - - public String syncBootstrap() throws HttpException, IOException { - return WithHttp.i().syncGet(HOST + "/reader/api/0/bootstrap", null, authHeaders); - } - - public String clientLogin(String accountId, String accountPd) throws HttpException, IOException { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("Email", accountId); - builder.add("Passwd", accountPd); - KLog.e(HOST + "/accounts/ClientLogin" + accountId + "-" + accountPd); - return WithHttp.i().syncPost(HOST + "/accounts/ClientLogin", builder, authHeaders); - } - - public void clientLogin2(String accountId, String accountPd, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("Email", accountId); - builder.add("Passwd", accountPd); - KLog.e(HOST + "/accounts/ClientLogin" + accountId + "-" + accountPd); - WithHttp.i().asyncPost(HOST + "/accounts/ClientLogin", builder, authHeaders, cb); - } - - - public String fetchUserInfo() throws HttpException, IOException { - return WithHttp.i().syncGet(HOST + "/reader/api/0/user-info", null, authHeaders); - } - - public String syncTagList() throws HttpException, IOException { - return WithHttp.i().syncGet(HOST + "/reader/api/0/tag/list", null, authHeaders); - } - - public String syncSubList() throws HttpException, IOException { - return WithHttp.i().syncGet(HOST + "/reader/api/0/subscription/list", null, authHeaders); - } - - public String syncStreamPrefs() throws HttpException, IOException { - return WithHttp.i().syncGet(HOST + "/reader/api/0/preference/stream/list", null, authHeaders); - } - - public String syncUnreadCounts() throws HttpException, IOException { - return WithHttp.i().syncGet(HOST + "/reader/api/0/unread-count", null, authHeaders); - } - - public String syncUnReadRefs(String continuation) throws HttpException, IOException { - HttpParams httpParams = new HttpParams(); -// addHeader("ot","0"); - httpParams.put("n", "1000"); - httpParams.put("xt", "user/" + mUserID + "/state/com.google/read"); - httpParams.put("s", "user/" + mUserID + "/state/com.google/reading-list"); - httpParams.put("includeAllDirectStreamIds", "false"); - if (continuation != null) { - httpParams.put("c", continuation); - } - return WithHttp.i().syncGet(HOST + ITEM_IDS, httpParams, authHeaders); - } - - public String syncStarredRefs(String continuation) throws HttpException, IOException { - HttpParams httpParams = new HttpParams(); -// addHeader("ot","0"); - httpParams.put("n", "1000"); - httpParams.put("s", "user/" + mUserID + "/state/com.google/starred"); - httpParams.put("includeAllDirectStreamIds", "false"); - httpParams.put("c", continuation); - return WithHttp.i().syncGet(HOST + ITEM_IDS, httpParams, authHeaders); - } - - public String syncItemContents(List ids) throws HttpException, IOException { - FormBody.Builder builder = new FormBody.Builder(); - for (String id : ids) { - builder.add("i", id); - } - return syncItemContents(builder); - } - - - public String syncItemContents(FormBody.Builder builder) throws HttpException, IOException { - return WithHttp.i().syncPost(HOST + ITEM_CONTENTS, builder, authHeaders); - } - - public String syncStaredStreamContents(String continuation) throws HttpException, IOException { - HttpParams httpParams = new HttpParams(); - httpParams.put("n", fetchContentCntForEach); - httpParams.put("r", "o"); - httpParams.put("c", continuation); - return WithHttp.i().syncGet(HOST + "/reader/api/0/stream/contents/" + "user/-/state/com.google/starred", httpParams, authHeaders); - } - - -// public String syncStarredContents( String continuation, NetCallbackS cb ) throws HttpException,IOException{ -// HttpParams httpParams = new HttpParams(); -// httpParams.put("n", "20"); -// httpParams.put("r", "o"); -// httpParams.put("c", continuation); -// return WithHttp.i().syncGet( HOST + "/reader/api/0/stream/contents/" + "user/-/state/com.google/starred", httpParams, authHeaders, cb); -// } - - - - - public void articleRemoveTag(String articleID, String tagId, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("r", tagId); - builder.add("i", articleID); - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void articleAddTag(String articleID, String tagId, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("a", tagId); - builder.add("i", articleID); - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void renameTag(String sourceTagId, String destTagId, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("s", sourceTagId); - builder.add("dest", destTagId); - WithHttp.i().asyncPost(HOST + RENAME_TAG, builder, authHeaders, cb); - } - - public void addFeed(String feedId, StringCallback cb) { - // /reader/api/0/subscription/quickadd - FormBody.Builder builder = new FormBody.Builder(); - builder.add("quickadd", feedId); - WithHttp.i().asyncPost(HOST + ADD_FEED, builder, authHeaders, cb); - } - - public void renameFeed(String feedId, String renamedTitle, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); -// builder.add("ac", "edit"); // 可省略 - builder.add("s", feedId); - builder.add("t", renamedTitle); - WithHttp.i().asyncPost(HOST + EDIT_FEED, builder, authHeaders, cb); - } - - public void renameFeed(OkHttpClient httpClient, String feedId, String renamedTitle, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); -// builder.add("ac", "edit"); // 可省略 - builder.add("s", feedId); - builder.add("t", renamedTitle); - WithHttp.i().asyncPost(httpClient, HOST + EDIT_FEED, builder, authHeaders, cb); - } - - public void editFeed(FormBody.Builder builder, StringCallback cb) { - WithHttp.i().asyncPost(HOST + EDIT_FEED, builder, authHeaders, cb); - } - public void unsubscribeFeed(String feedId, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("ac", "unsubscribe"); - builder.add("s", feedId); - WithHttp.i().asyncPost(HOST + EDIT_FEED, builder, authHeaders, cb); - } - - public void unsubscribeFeed(OkHttpClient httpClient, String feedId, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("ac", "unsubscribe"); - builder.add("s", feedId); - WithHttp.i().asyncPost(httpClient, HOST + EDIT_FEED, builder, authHeaders, cb); - } - - public void markArticleListReaded(List articleIDs, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("a", "user/-/state/com.google/read"); - for (String articleID : articleIDs) { - builder.add("i", articleID); - } - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void markArticleReaded(String articleID, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("a", "user/-/state/com.google/read"); - builder.add("i", articleID); - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void markArticleReaded(OkHttpClient httpClient, String articleID, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("a", "user/-/state/com.google/read"); - builder.add("i", articleID); - WithHttp.i().asyncPost(httpClient, HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void markArticleUnread(String articleID, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("r", "user/-/state/com.google/read"); - builder.add("i", articleID); - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void markArticleUnstar(String articleID, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("r", "user/-/state/com.google/starred"); - builder.add("i", articleID); - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - public void markArticleStared(String articleID, StringCallback cb) { - FormBody.Builder builder = new FormBody.Builder(); - builder.add("a", "user/-/state/com.google/starred"); - builder.add("i", articleID); - WithHttp.i().asyncPost(HOST + EDIT_TAG, builder, authHeaders, cb); - } - - - public static boolean isFeed(String id) { - return id.startsWith("feed/"); - } - - public static boolean isTag(String id) { - return id.startsWith("user/"); - } - - - public static boolean isLabel(String paramString) { - String REGEX_LABEL = "^user\\/\\d{0,12}\\/label\\/"; // user/1006097346/label/tag_name - Pattern p_label = Pattern.compile(REGEX_LABEL, Pattern.CASE_INSENSITIVE); - Matcher m_label = p_label.matcher(paramString); - return m_label.find(); - } - - public static boolean isState(String paramString) { - String REGEX_LABEL = "^user\\/\\d{0,12}\\/state\\/"; - Pattern p = Pattern.compile(REGEX_LABEL, Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(paramString); - return m.find(); - } - - -} diff --git a/app/src/main/java/me/wizos/loread/net/MercuryApi.java b/app/src/main/java/me/wizos/loread/net/MercuryApi.java deleted file mode 100644 index c2c5b13..0000000 --- a/app/src/main/java/me/wizos/loread/net/MercuryApi.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.wizos.loread.net; - -import com.lzy.okgo.OkGo; -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.https.HttpsUtils; -import com.lzy.okgo.model.HttpHeaders; - -import java.util.concurrent.TimeUnit; - -import okhttp3.OkHttpClient; - -/** - * @author Wizos on 2017/12/17. - */ - -public class MercuryApi { - private static final String KEY = "183CSA5SmIlO3Utl77XrsCEMe6W5Ap2EkVAM8ccI"; - private static final String HOST = "https://mercury.postlight.com/parser?url="; - - public static void fetchReadabilityContent(String url, StringCallback cb) { - HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(); - OkHttpClient httpClient = new OkHttpClient.Builder() - .readTimeout(30000L, TimeUnit.MILLISECONDS) - .writeTimeout(30000L, TimeUnit.MILLISECONDS) - .connectTimeout(30000L, TimeUnit.MILLISECONDS) - .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager) - .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) - .build(); - HttpHeaders headers = new HttpHeaders(); - headers.put("Content-Type", "application/json"); - headers.put("x-api-key", KEY); - OkGo.get(HOST + url) - .headers(headers) - .client(httpClient) - .execute(cb); - } -} diff --git a/app/src/main/java/me/wizos/loread/net/NetCallbackS.java b/app/src/main/java/me/wizos/loread/net/NetCallbackS.java deleted file mode 100644 index 8d6b144..0000000 --- a/app/src/main/java/me/wizos/loread/net/NetCallbackS.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.wizos.loread.net; - - -import com.lzy.okgo.request.base.Request; - -/** - * 该类作为网络请求时的各种回调接口 - * Created by Wizos on 2017/10/2. - */ -/* -Code Description -200 Request OK -400 Mandatory parameter(s) missing -401 End-user not authorized -403 You are not sending the correct AppID and/or AppSecret -404 Method not implemented -429 Daily limit reached for this zone -503 Service unavailable - */ -public abstract class NetCallbackS { - - public void onSuccess(String body) { - } - - public void onFailure(Request request) { - } -// -// public void onSuccess( Response response ) { -// if(response.code()!=200){ -// ToastUtil.showLong("返回不是Success"); -// onFailure(response); -// } -// } - -// public void onFailure( Response response ) { -// } -} diff --git a/app/src/main/java/me/wizos/loread/net/SearchApi.java b/app/src/main/java/me/wizos/loread/net/SearchApi.java deleted file mode 100644 index bde16cc..0000000 --- a/app/src/main/java/me/wizos/loread/net/SearchApi.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.wizos.loread.net; - -import com.lzy.okgo.callback.StringCallback; - -/** - * Created by Wizos on 2017/12/31. - */ - -public class SearchApi { - // private static final String Loreead_Feeds_Host = "http://api.wizos.me/search.php"; - private static final String Feedly_Feeds_Host = "http://cloud.feedly.com/v3/search/feeds"; -// private static final String Inoreader_Feeds_Host = "https://www.inoreader.com"; - private static final int page_unit = 10000; - private static SearchApi searchApi; - - public static SearchApi i() { - if (searchApi == null) { - synchronized (InoApi.class) { - if (searchApi == null) { - searchApi = new SearchApi(); - } - } - } - return searchApi; - } - - public void asyncFetchSearchResult(String searchWord, StringCallback cb) { -// WithHttp.i().asyncGet(Loreead_Feeds_Host + "?n=" + page_unit + "&q=" + searchWord, null, null, cb ); -// WithHttp.i().asyncGet(Loreead_Feeds_Host + "?n=" + page_unit + "&q=" + searchWord, null, null, cb ); - -// if(BuildConfig.DEBUG){ -// FormBody.Builder builder = new FormBody.Builder(); -// builder.add("n", page_unit + ""); -// builder.add("q", searchWord); -// WithHttp.i().asyncPost(Loreead_Feeds_Host, builder, null, cb); -// }else { - WithHttp.i().asyncGet(Feedly_Feeds_Host + "?n=" + page_unit + "&q=" + searchWord, null, null, cb); -// } - } - -// public FeedlyFeedsSearchResult fetchSearchResult(String searchWord ) throws IOException{ -// String json = WithHttp.i().syncGet(Loreead_Feeds_Host + "?n=" + page_unit + "&q=" + searchWord, null, null, null); -// FeedlyFeedsSearchResult searchResult = new Gson().fromJson(json, FeedlyFeedsSearchResult.class ); -// KLog.e(searchResult.getHint()); -// KLog.e(searchResult.getResults().size()); -// return searchResult; -//// return new FeedlyFeedsSearchResult(); -// } -// public void syncSearchFeeds(String searchWord,int page_unit, NetCallbackS cb) throws HttpException, IOException { -// String json = WithHttp.i().syncPost(Inoreader_Feeds_Host + "/reader/api/0/directory/search/"+ searchWord + "?n=" + page_unit , null, null, cb); -// InoreaderFeedsSearchResult searchFeedsResult = new Gson().fromJson(json, InoreaderFeedsSearchResult.class ); -// ArrayList feeds = searchFeedsResult.getFeeds(); -// int field = 0; -// FeedlyFeed feedlyFeed; -// ArrayList feedlyFeeds = new ArrayList<>(feeds.size()); -// for (InoreaderFeed feed:feeds){ -// field = searchMap.get("feed/" + feed.getXmlUrl()); -// if( field != 0 ){ -// }else { -// feedlyFeed = new FeedlyFeed(); -// feedlyFeed.setFeedId( "feed/" + feed.getXmlUrl() ); -// feedlyFeed.setTitle( feed.getTitle() ); -// feedlyFeed.setSubscribers( feed.getSubscribers() ); -// feedlyFeed.setVelocity( feed.getArticlesPerWeek() + ""); -// feedlyFeed.setVisualUrl( feed.getIconUrl() ); -// feedlyFeeds.add(feedlyFeed); -// searchMap.put( "feed/" + feed.getXmlUrl(),1 ); -// } -// } -// } - -} diff --git a/app/src/main/java/me/wizos/loread/net/WithHttp.java b/app/src/main/java/me/wizos/loread/net/WithHttp.java deleted file mode 100644 index 649259c..0000000 --- a/app/src/main/java/me/wizos/loread/net/WithHttp.java +++ /dev/null @@ -1,127 +0,0 @@ -package me.wizos.loread.net; - -import com.lzy.okgo.OkGo; -import com.lzy.okgo.callback.StringCallback; -import com.lzy.okgo.exception.HttpException; -import com.lzy.okgo.model.HttpHeaders; -import com.lzy.okgo.model.HttpParams; -import com.lzy.okgo.model.Response; -import com.lzy.okgo.request.GetRequest; -import com.lzy.okgo.request.PostRequest; -import com.socks.library.KLog; - -import java.io.IOException; - -import me.wizos.loread.utils.ToastUtil; -import okhttp3.FormBody; -import okhttp3.OkHttpClient; - -/** - * Created by Wizos on 2017/10/12. - */ - -public class WithHttp { - private static WithHttp withHttp; - - private WithHttp() { - } - - public static WithHttp i() { - if (withHttp == null) { - synchronized (WithHttp.class) { - if (withHttp == null) { - withHttp = new WithHttp(); - } - } - } - return withHttp; - } - - public String syncGet(String url, HttpParams httpParams, HttpHeaders httpHeaders) throws HttpException, IOException { - KLog.e("开始同步网络" + url); - GetRequest get = OkGo.get(url); - get.tag(url); - get.params(httpParams); - get.headers(httpHeaders); - okhttp3.Response response = get.execute(); - if (response.isSuccessful()) { - return response.body().string(); - } else { - throw new HttpException(""); - } - } - - // 同步的获取数据 - public String syncPost(String url, FormBody.Builder bodyBuilder, HttpHeaders httpHeaders) throws HttpException, IOException { - PostRequest post = OkGo.post(url); - post.tag(url); - if (bodyBuilder != null) { - post.upRequestBody(bodyBuilder.build()); - } - post.headers(httpHeaders); - okhttp3.Response response = post.execute(); - return response.body().string(); - } - - - public void asyncGet(String url, HttpParams httpParams, HttpHeaders httpHeaders, StringCallback cb) { - GetRequest get = OkGo.get(url); - get.tag(url); - get.params(httpParams); - get.headers(httpHeaders); - get.execute(cb); - } - - public void asyncPost(String url, FormBody.Builder bodyBuilder, HttpHeaders httpHeaders, StringCallback cb) { - if (cb == null) { - cb = new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - } - } - - @Override - public void onError(Response response) { - ToastUtil.showLong("文章状态同步失败,请稍后再试"); - } - }; - } - PostRequest post = OkGo.post(url); - post.tag(url); - if (bodyBuilder != null) { - post.upRequestBody(bodyBuilder.build()); - } - post.headers(httpHeaders); - post.execute(cb); - } - - public void asyncPost(OkHttpClient httpClient, String url, FormBody.Builder bodyBuilder, HttpHeaders httpHeaders, StringCallback cb) { - if (cb == null) { - cb = new StringCallback() { - @Override - public void onSuccess(Response response) { - if (!response.body().equals("OK")) { - this.onError(response); - } - } - - @Override - public void onError(Response response) { - ToastUtil.showLong("文章状态同步失败,请稍后再试"); - } - }; - } - PostRequest post = OkGo.post(url); - if (bodyBuilder != null) { - post.upRequestBody(bodyBuilder.build()); - } - post.tag(url) - .client(httpClient) - .headers(httpHeaders) - .execute(cb); - } -} - - diff --git a/app/src/main/java/me/wizos/loread/network/HttpClientManager.java b/app/src/main/java/me/wizos/loread/network/HttpClientManager.java new file mode 100644 index 0000000..e4575ab --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/HttpClientManager.java @@ -0,0 +1,140 @@ +package me.wizos.loread.network; + +import com.lzy.okgo.https.HttpsUtils; + +import java.util.concurrent.TimeUnit; + +import me.wizos.loread.network.interceptor.InoreaderHeaderInterceptor; +import me.wizos.loread.network.interceptor.LoggerInterceptor; +import me.wizos.loread.network.interceptor.LoreadTokenInterceptor; +import me.wizos.loread.network.interceptor.RedirectInterceptor; +import me.wizos.loread.network.interceptor.RelyInterceptor; +import me.wizos.loread.network.interceptor.TTRSSTokenInterceptor; +import me.wizos.loread.network.interceptor.TokenAuthenticator; +import okhttp3.OkHttpClient; + +/** + * @author Wizos on 2019/5/12. + */ + +public class HttpClientManager { + private HttpClientManager() { + } + + private static HttpClientManager instance; + private static OkHttpClient simpleOkHttpClient; + private static OkHttpClient loreadHttpClient; + private static OkHttpClient ttrssHttpClient; + private static OkHttpClient inoreaderHttpClient; + private static OkHttpClient feedlyHttpClient; + private static OkHttpClient imageHttpClient; + + public static HttpClientManager i() { + if (instance == null) { + synchronized (HttpClientManager.class) { + if (instance == null) { + instance = new HttpClientManager(); + + loreadHttpClient = new OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) + .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) + .followRedirects(true) + .followSslRedirects(true) + .addInterceptor(new LoreadTokenInterceptor()) + .build(); + ttrssHttpClient = new OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) + .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) + .followRedirects(true) + .followSslRedirects(true) +// .authenticator(new TTRSSAuthenticator()) + .addInterceptor(new TTRSSTokenInterceptor()) + .build(); + + inoreaderHttpClient = new OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) + .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) + .followRedirects(true) + .followSslRedirects(true) +// .addInterceptor(new AuthorizationInterceptor()) + .addInterceptor(new InoreaderHeaderInterceptor()) + .addInterceptor(new LoggerInterceptor()) + .authenticator(new TokenAuthenticator()) +// .dns(new HttpDNS()) + .build(); + + + feedlyHttpClient = new OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) + .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) + .followRedirects(true) + .followSslRedirects(true) +// .addInterceptor(new AuthorizationInterceptor()) + .addInterceptor(new LoggerInterceptor()) + .authenticator(new TokenAuthenticator()) + .build(); + + simpleOkHttpClient = new OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) + .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) + .followRedirects(true) + .followSslRedirects(true) + .addInterceptor(new RelyInterceptor()) + .build(); + imageHttpClient = new OkHttpClient.Builder() + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) + .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier) + .followRedirects(true) + .followSslRedirects(true) + .addInterceptor(new RedirectInterceptor()) +// .addInterceptor(new RefererInterceptor()) + .build(); + imageHttpClient.dispatcher().setMaxRequests(4); + } + } + } + return instance; + } + + + public OkHttpClient simpleClient() { + return simpleOkHttpClient; + } + + public OkHttpClient loreadHttpClient() { + return loreadHttpClient; + } + + public OkHttpClient ttrssHttpClient() {return ttrssHttpClient;} + + public OkHttpClient inoreaderHttpClient() { + return inoreaderHttpClient; + } + + public OkHttpClient feedlyHttpClient() { + return feedlyHttpClient; + } + + public OkHttpClient imageHttpClient() { + return imageHttpClient; + } + +} diff --git a/app/src/main/java/me/wizos/loread/network/StringConverterFactory.java b/app/src/main/java/me/wizos/loread/network/StringConverterFactory.java new file mode 100644 index 0000000..1204ecf --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/StringConverterFactory.java @@ -0,0 +1,38 @@ +package me.wizos.loread.network; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; + +public class StringConverterFactory extends Converter.Factory { + //工厂方法,用于创建实例 + public static StringConverterFactory create() { + return new StringConverterFactory(); + } + + //response返回到本地后会被调用,这里先判断是否要拦截处理,不拦截则返回null + // 判断是否处理的依据就是type参数,type就是上面接口出现的List了 + @Override + public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { + // KLog.e("响应的类型:" + type ); + if (type == String.class) { + return new StringBodyConverter(); + } + //如果返回null则不处理,交给别的Converter处理 + return null; + } + + // 一个Converter类,T就是上面接口中的List了 + private static class StringBodyConverter implements Converter { + StringBodyConverter() {} + //在这个方法中处理response + @Override + public T convert(ResponseBody value) throws IOException { + return (T) value.string(); + } + } +} diff --git a/app/src/main/java/me/wizos/loread/network/SyncWorker.java b/app/src/main/java/me/wizos/loread/network/SyncWorker.java new file mode 100644 index 0000000..10518b7 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/SyncWorker.java @@ -0,0 +1,39 @@ +package me.wizos.loread.network; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.jeremyliao.liveeventbus.LiveEventBus; + +import me.wizos.loread.App; +import me.wizos.loread.utils.NetworkUtil; + +public class SyncWorker extends Worker { + public final static String TAG = "SyncWorker"; + public final static String SYNC_TASK_STATUS = "SyncStatus"; + public final static String SYNC_PROCESS_FOR_SUBTITLE = "SyncProcess"; + public final static String NEW_ARTICLE_NUMBER = "NewArticleNumber"; + public SyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + if(!App.i().getUser().isAutoSync() || (App.i().getUser().isAutoSyncOnlyWifi() && !NetworkUtil.isWiFiUsed()) ){ + return Result.success(); + } + LiveEventBus.get(SyncWorker.SYNC_TASK_STATUS).post(true); + App.i().getApi().sync(); + LiveEventBus.get(SyncWorker.SYNC_TASK_STATUS).post(false); + return Result.success(); + } + + @Override + public void onStopped() { + super.onStopped(); + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/AuthApi.java b/app/src/main/java/me/wizos/loread/network/api/AuthApi.java new file mode 100644 index 0000000..d9bfa0e --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/AuthApi.java @@ -0,0 +1,11 @@ +package me.wizos.loread.network.api; + +public abstract class AuthApi extends BaseApi { + private String authorization; + public void setAuthorization(String authorization){ + this.authorization = authorization; + } + public String getAuthorization(){ + return authorization; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/BaseApi.java b/app/src/main/java/me/wizos/loread/network/api/BaseApi.java new file mode 100644 index 0000000..b6cf9aa --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/BaseApi.java @@ -0,0 +1,324 @@ +package me.wizos.loread.network.api; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import com.socks.library.KLog; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import me.wizos.loread.App; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.config.ArticleTags; +import me.wizos.loread.config.SaveDirectory; +import me.wizos.loread.config.Unsubscribe; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.ArticleTag; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.FeedCategory; +import me.wizos.loread.db.Tag; +import me.wizos.loread.network.HttpClientManager; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.ArticleUtil; +import me.wizos.loread.utils.EncryptUtil; +import me.wizos.loread.utils.FileUtil; +import me.wizos.loread.utils.StringUtils; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Request; + +/** + * Created by Wizos on 2019/2/10. + */ + +public abstract class BaseApi { + int fetchContentCntForEach = 20; // 每次获取内容的数量 + + // 同步所有数据,此处应该传入一个进度监听器,或者直接用EventBus发消息 + abstract public void sync(); + + abstract public void fetchUserInfo(CallbackX cb); + + abstract public void renameTag(String sourceTagId, String destTagId, CallbackX cb); + + abstract public void editFeedCategories(List lastCategoryItems, EditFeed editFeed, CallbackX cb); + + abstract public void addFeed(EditFeed editFeed, CallbackX cb); + + abstract public void unsubscribeFeed(String feedId, CallbackX cb); + + abstract public void renameFeed(String feedId, String renamedTitle, CallbackX cb); + + abstract public void markArticleReaded(String articleId, CallbackX cb); + + abstract public void markArticleUnread(String articleId, CallbackX cb); + + abstract public void markArticleStared(String articleId, CallbackX cb); + + abstract public void markArticleUnstar(String articleId, CallbackX cb); + + abstract public void markArticleListReaded(List articleIds, CallbackX cb); + + public interface ArticleChanger { + Article change(Article article); + } + + + void deleteExpiredArticles() { + // 最后的 300 * 1000L 是留前5分钟时间的不删除 WithPref.i().getClearBeforeDay() + long time = System.currentTimeMillis() - App.i().getUser().getCachePeriod() * 24 * 3600 * 1000L - 300 * 1000L; + String uid = App.i().getUser().getId(); + + List
boxReadArts = CoreDB.i().articleDao().getReadedUnstarBeFiledLtTime(uid, time); + //KLog.i("移动文章" + boxReadArts.size()); + for (Article article : boxReadArts) { + article.setSaveStatus(App.STATUS_IS_FILED); + String dir = "/" + SaveDirectory.i().getSaveDir(article.getFeedId(),article.getId()) + "/"; + //KLog.e("保存目录:" + dir); + FileUtil.saveArticle(App.i().getUserBoxPath() + dir, article); + } + CoreDB.i().articleDao().update(boxReadArts); + + List
storeReadArts = CoreDB.i().articleDao().getReadedStaredBeFiledLtTime(uid, time); + //KLog.i("移动文章" + storeReadArts.size()); + for (Article article : storeReadArts) { + article.setSaveStatus(App.STATUS_IS_FILED); + String dir = "/" + SaveDirectory.i().getSaveDir(article.getFeedId(),article.getId()) + "/"; + //KLog.e("保存目录:" + dir); + FileUtil.saveArticle(App.i().getUserStorePath() + dir, article); + } + CoreDB.i().articleDao().update(storeReadArts); + + List
expiredArticles = CoreDB.i().articleDao().getReadedUnstarLtTime(uid, time); + ArrayList idListMD5 = new ArrayList<>(expiredArticles.size()); + for (Article article : expiredArticles) { + idListMD5.add(EncryptUtil.MD5(article.getId())); + } + //KLog.i("清除A:" + time + "--" + expiredArticles.size()); + FileUtil.deleteHtmlDirList(idListMD5); + CoreDB.i().articleDao().delete(expiredArticles); + } + + void fetchReadability(String uid, long syncTimeMillis){ + List
articles = CoreDB.i().articleDao().getNeedReadability(uid,syncTimeMillis); + for (Article article : articles) { + //KLog.e("====获取:" + entry.getKey() + " , " + article.getTitle() + " , " + article.getLink()); + if (TextUtils.isEmpty(article.getLink())) { + continue; + } + //KLog.e("====开始请求" ); + Request request = new Request.Builder().url(article.getLink()).build(); + Call call = HttpClientManager.i().simpleClient().newCall(request); + call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, IOException e) { + KLog.e("获取失败"); + } + + // 在Android应用中直接使用上述代码进行异步请求,并且在回调方法中操作了UI,那么你的程序就会抛出异常,并且告诉你不能在非UI线程中操作UI。 + // 这是因为OkHttp对于异步的处理仅仅是开启了一个线程,并且在线程中处理响应。 + // OkHttp是一个面向于Java应用而不是特定平台(Android)的框架,那么它就无法在其中使用Android独有的Handler机制。 + @Override + public void onResponse(@NotNull Call call, @NotNull okhttp3.Response response) throws IOException { + if (response.isSuccessful()) { +// Pattern pattern = Pattern.compile("", Pattern.CASE_INSENSITIVE); +// String content = pattern.matcher(article.getContent()).replaceAll("_|_|_"); +// content = Jsoup.parseBodyFragment(content).text(); +// String[] flags = content.split("_\\|_\\|_"); +// String flag = ""; +// if(flags.length >= 1){ +// flag = flags[0]; +// if(flag.length() > 8){ +// flag = flag.substring(0,8); +// } +// } + Article optimizedArticle = ArticleUtil.getReadabilityArticle(article,response.body()); + CoreDB.i().articleDao().update(optimizedArticle); + } + } + }); + } + } + + + void handleNotTagStarArticles(String uid, long syncTimeMillis){ + List
articles = CoreDB.i().articleDao().getNotTagStar(uid,syncTimeMillis); + List articleTags = new ArrayList<>(); + Set tagTitleSet = new HashSet<>(); + for (Article article: articles){ + if(StringUtils.isEmpty(article.getFeedId())){ + continue; + } + List categories = CoreDB.i().categoryDao().getByFeedId(uid,article.getFeedId()); + for (Category category:categories) { + articleTags.add( new ArticleTag(uid, article.getId(), category.getId()) ); + tagTitleSet.add(category.getTitle()); + } + } + CoreDB.i().articleTagDao().insert(articleTags); + + List tags = new ArrayList<>(tagTitleSet.size()); + for (String title:tagTitleSet) { + Tag tag = new Tag(); + tag.setUid(uid); + tag.setId(title); + tag.setTitle(title); + tags.add(tag); + KLog.e("设置 Tag 数据:" + tag); + } + CoreDB.i().tagDao().insert(tags); + CoreDB.i().articleTagDao().insert(articleTags); + ArticleTags.i().addArticleTags(articleTags); + ArticleTags.i().save(); + } + + void clearNotArticleTags(String uid){ + List articleTags = CoreDB.i().articleTagDao().getNotArticles(uid); + for (ArticleTag articleTag:articleTags) { + CoreDB.i().articleTagDao().delete(articleTag); + } + } + + void coverSaveCategories(List cloudyCategories) { + String uid = App.i().getUser().getId(); + ArrayMap cloudyCategoriesTmp = new android.util.ArrayMap<>(cloudyCategories.size()); + for (Category category : cloudyCategories) { + category.setUid(uid); + cloudyCategoriesTmp.put(category.getId(), category); + } + + List localCategories = CoreDB.i().categoryDao().getAll(uid); + Iterator iterator = localCategories.iterator(); + Category tmpCategory; + while(iterator.hasNext()){ + tmpCategory = iterator.next(); + if (cloudyCategoriesTmp.get(tmpCategory.getId()) == null) { + CoreDB.i().categoryDao().delete(tmpCategory); + iterator.remove(); + }else { + cloudyCategoriesTmp.remove(tmpCategory.getId()); + } + } + + cloudyCategories.clear(); + for (Map.Entry entry: cloudyCategoriesTmp.entrySet()) { + cloudyCategories.add(entry.getValue()); + } + + CoreDB.i().categoryDao().insert(localCategories); + CoreDB.i().categoryDao().insert(cloudyCategories); + } + + void coverSaveFeeds(List cloudyFeeds) { + String uid = App.i().getUser().getId(); + ArrayMap cloudyMap = new android.util.ArrayMap<>(cloudyFeeds.size()); + for (Feed feed : cloudyFeeds) { + feed.setUid(uid); + cloudyMap.put(feed.getId(), feed); + } + + List localFeeds = CoreDB.i().feedDao().getAll(uid); + List deleteFeeds = new ArrayList<>(); + Iterator iterator = localFeeds.iterator(); + Feed localFeed, commonFeed; + while(iterator.hasNext()){ + localFeed = iterator.next(); + commonFeed = cloudyMap.get(localFeed.getId()); + if ( commonFeed == null) { + CoreDB.i().feedCategoryDao().deleteByFeedId(localFeed.getUid(), localFeed.getId()); + CoreDB.i().articleDao().deleteUnStarByFeedId(localFeed.getUid(), localFeed.getId()); + CoreDB.i().feedDao().delete(localFeed); + deleteFeeds.add(localFeed); + iterator.remove();// 删除后,这里只剩2者的交集 +// KLog.e("删除本地的feed:" + localFeed.getId() + " , " + localFeed.getTitle() + " , " + localFeed.getUnreadCount() ); + }else { + localFeed.setTitle(commonFeed.getTitle()); + localFeed.setFeedUrl(commonFeed.getFeedUrl()); + localFeed.setHtmlUrl(commonFeed.getHtmlUrl()); + cloudyMap.remove(localFeed.getId()); + } + } + cloudyFeeds.clear(); + for (Map.Entry entry: cloudyMap.entrySet()) { + cloudyFeeds.add(entry.getValue()); + } + + CoreDB.i().feedDao().insert(localFeeds); + CoreDB.i().feedDao().insert(cloudyFeeds); + Unsubscribe.genBackupFile2(App.i().getUser(), deleteFeeds); + } + + void coverFeedCategory(List cloudyFeedCategories) { + ArrayMap cloudyCategoriesTmp = new ArrayMap<>(cloudyFeedCategories.size()); + for (FeedCategory feedCategory : cloudyFeedCategories) { + cloudyCategoriesTmp.put(feedCategory.getFeedId() + feedCategory.getCategoryId(), feedCategory); + } + + List localFeedCategories = CoreDB.i().feedCategoryDao().getAll(App.i().getUser().getId()); + FeedCategory tmp; + + for (FeedCategory feedCategory : localFeedCategories) { + tmp = cloudyCategoriesTmp.get(feedCategory.getFeedId() + feedCategory.getCategoryId()); + if (tmp == null) { + CoreDB.i().feedCategoryDao().delete(feedCategory); + } else { + cloudyFeedCategories.remove(tmp); + } + } + CoreDB.i().feedCategoryDao().insert(cloudyFeedCategories); + } + + + + void handleDuplicateArticle() { + // 清理重复的文章 + Article articleSample; + List links = CoreDB.i().articleDao().getDuplicatesLink(App.i().getUser().getId()); + List
articleList; + for (String link : links) { + articleList = CoreDB.i().articleDao().getDuplicates(App.i().getUser().getId(), link); + if (articleList == null || articleList.size() == 0) { + continue; + } + // KLog.e("获取到的重复文章数量:" + articleList.size()); + // 获取第一个作为范例 + articleSample = articleList.get(0); + articleList.remove(0); + + List
articles = new ArrayList<>(); + for (Article article: articleList) { + if( articleSample.getCrawlDate() != article.getCrawlDate()){ + article.setCrawlDate(articleSample.getCrawlDate()); + article.setPubDate(articleSample.getPubDate()); + articles.add(article); + } + } + CoreDB.i().articleDao().update(articles); + } + } + + // 优化在使用状态下多次同步到新文章时,这些文章的爬取时间 + void handleCrawlDate(){ + String uid = App.i().getUser().getId(); + long lastReadMarkTimeMillis = CoreDB.i().articleDao().getLastReadTimeMillis(uid); + long lastStarMaskTimeMillis = CoreDB.i().articleDao().getLastStarTimeMillis(uid); + long lastMarkTimeMillis = Math.max(lastReadMarkTimeMillis,lastStarMaskTimeMillis); + CoreDB.i().articleDao().updateIdleCrawlDate(uid, lastMarkTimeMillis, System.currentTimeMillis()); + } + + void updateCollectionCount() { + String uid = App.i().getUser().getId(); + CoreDB.i().feedDao().update( CoreDB.i().feedDao().getFeedsRealTimeCount(uid) ); + CoreDB.i().categoryDao().update( CoreDB.i().categoryDao().getCategoriesRealTimeCount(uid) ); + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/FeedlyApi.java b/app/src/main/java/me/wizos/loread/network/api/FeedlyApi.java new file mode 100644 index 0000000..45f188f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/FeedlyApi.java @@ -0,0 +1,689 @@ +package me.wizos.loread.network.api; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArrayMap; + +import com.google.gson.GsonBuilder; +import com.hjq.toast.ToastUtils; +import com.jeremyliao.liveeventbus.LiveEventBus; +import com.lzy.okgo.exception.HttpException; +import com.socks.library.KLog; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.bean.Token; +import me.wizos.loread.bean.feedly.CategoryItem; +import me.wizos.loread.bean.feedly.Collection; +import me.wizos.loread.bean.feedly.Entry; +import me.wizos.loread.bean.feedly.FeedItem; +import me.wizos.loread.bean.feedly.Profile; +import me.wizos.loread.bean.feedly.StreamIds; +import me.wizos.loread.bean.feedly.input.EditCollection; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.bean.feedly.input.MarkerAction; +import me.wizos.loread.config.ArticleActionConfig; +import me.wizos.loread.config.LinkRewriteConfig; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.FeedCategory; +import me.wizos.loread.db.User; +import me.wizos.loread.network.HttpClientManager; +import me.wizos.loread.network.SyncWorker; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.StringUtils; +import okhttp3.FormBody; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +import static me.wizos.loread.utils.StringUtils.getString; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class FeedlyApi extends OAuthApi { + private static final String APP_ID = "palabre"; + private static final String APP_KEY = "FE01H48LRK62325VQVGYOZ24YFZL"; + private static final String OFFICIAL_BASE_URL = "https://feedly.com/v3"; + private static final String REDIRECT_URI = "palabre://feedlyauth"; + + // 系统默认的分类 + // user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/category/global.must + // user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/category/global.uncategorized + // user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/category/global.all + // user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/tag/global.unsaved // 取消稍后读 + // user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/tag/global.saved // 稍后读(加星) + // user/12cc057f-9891-4ab3-99da-86f2dee7f2f5/tag/杂 // tag + + private Retrofit retrofit; + private FeedlyService service; + + public FeedlyApi() { + String baseUrl = LinkRewriteConfig.i().getRedirectUrl(FeedlyApi.OFFICIAL_BASE_URL); + if(StringUtils.isEmpty(baseUrl)){ + baseUrl = FeedlyApi.OFFICIAL_BASE_URL; + } + retrofit = new Retrofit.Builder() + .baseUrl(baseUrl + "/") // 设置网络请求的Url地址, 必须以/结尾 + .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 设置数据解析器 + .client(HttpClientManager.i().feedlyHttpClient()) + .build(); + service = retrofit.create(FeedlyService.class); + } + + @Override + public void setAuthorization(String authorization) { + super.setAuthorization(authorization); + } + + public String getOAuthUrl() { + String baseUrl = LinkRewriteConfig.i().getRedirectUrl(OFFICIAL_BASE_URL); + if(StringUtils.isEmpty(baseUrl)){ + baseUrl = OFFICIAL_BASE_URL; + } + + if (!baseUrl.endsWith("/")) { + baseUrl = baseUrl + "/"; + } + return baseUrl + "auth/auth?response_type=code&client_id=palabre&scope=https://cloud.feedly.com/subscriptions&redirect_uri=palabre://feedlyauth&state=/profile"; +// String redirectUri = "loread://oauth"; +// String url = "https://cloud.feedly.com/v3/auth/auth?response_type=code&client_id=" + clientId + "&scope=https://cloud.feedly.com/subscriptions&redirect_uri=" + redirectUri + "&state=/profile"; +// return HOST + "/auth/auth?response_type=code&client_id=" + APP_ID + "&redirect_uri=" + redirectUri + "&state=loread&scope=https://cloud.feedly.com/subscriptions"; + } + public void getAccessToken(String authorizationCode,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("grant_type", "authorization_code"); + builder.add("code", authorizationCode); + builder.add("redirect_uri", REDIRECT_URI); + builder.add("client_id", APP_ID); + builder.add("client_secret", APP_KEY); + + service.getAccessToken("authorization_code", REDIRECT_URI,APP_ID,APP_KEY,authorizationCode).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(retrofit2.Call call, Response response) { + if(response.isSuccessful()){ + cb.onSuccess(response.body()); + }else { + cb.onFailure("失败:" + response.message()); + } + } + + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + cb.onFailure("失败:" + t.getMessage()); + } + }); + } + + + public void refreshingAccessToken(String refreshToken, CallbackX cb) { + service.refreshingAccessToken("refresh_token",refreshToken,APP_ID,APP_KEY).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(retrofit2.Call call, Response response) { + if(response.isSuccessful() && response.body()!=null){ + if (TextUtils.isEmpty(response.body().getRefresh_token())) { + response.body().setRefresh_token(refreshToken); + } + User user = App.i().getUser(); + if (user != null) { + user.setToken(response.body()); + CoreDB.i().userDao().insert(user); + } + // 更新缓存中的授权 + ((FeedlyApi) App.i().getApi()).setAuthorization(App.i().getUser().getAuth()); + + cb.onSuccess(response.body()); + }else { + cb.onFailure("失败:" + response.message()); + } + } + + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + cb.onFailure("失败:" + t.getMessage()); + } + }); + } + public String refreshingAccessToken(String refreshToken) throws IOException { + Token token = service.refreshingAccessToken("refresh_token",refreshToken,APP_ID,APP_KEY).execute().body(); + if (TextUtils.isEmpty(token.getRefresh_token())) { + token.setRefresh_token(refreshToken); + } + User user = App.i().getUser(); + if (user != null) { + user.setToken(token); + CoreDB.i().userDao().insert(user); + } + // 更新缓存中的授权 + ((FeedlyApi) App.i().getApi()).setAuthorization(App.i().getUser().getAuth()); + return token.getAuth(); + } + + public void fetchUserInfo(CallbackX cb){ + service.getUserInfo(getAuthorization()).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(@NonNull retrofit2.Call call,@NonNull Response response) { + KLog.e("获取资料:" + response.isSuccessful() ); + if( response.isSuccessful()){ + cb.onSuccess(response.body().getUser()); + }else { + cb.onFailure("获取失败:" + response.message()); + } + } + + @Override + public void onFailure(@NonNull retrofit2.Call call,@NonNull Throwable t) { + cb.onFailure("获取失败:" + t.getMessage()); + } + }); + } + + @Override + public void sync() { + try { + long startSyncTimeMillis = System.currentTimeMillis(); + String uid = App.i().getUser().getId(); + + KLog.e("3 - 同步订阅源信息"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.sync_feed_info)); + + // 获取分类&feed + List collectionList = service.getCollections(getAuthorization()).execute().body(); + Iterator collectionsIterator = collectionList.iterator(); + Collection collection; + // 本地无该 category + ArrayList feedItems; + ArrayList categories = new ArrayList<>(); + CategoryItem categoryItem; + Feed feed; + Category category; + ArrayList feedCategories = new ArrayList<>(); + ArrayList feeds = new ArrayList<>(); + + while (collectionsIterator.hasNext()) { + collection = collectionsIterator.next(); + if (collection.getId().endsWith(App.CATEGORY_MUST)) { + collectionsIterator.remove(); //注意这个地方 + continue; + } + categoryItem = collection.getCategoryItem(); + feedItems = collection.getFeedItems(); + + category = categoryItem.convert(); + category.setUid(uid); + categories.add(category); + for (FeedItem feedItemTmp : feedItems) { + feed = feedItemTmp.convert2Feed(); + feed.setUid(uid); + feeds.add(feed); + feedCategories.add( new FeedCategory(uid, feedItemTmp.getId(), categoryItem.getId()) ); + } + } + + + // 如果在获取到数据的时候就保存,那么到这里同步断了的话,可能系统内的文章就找不到响应的分组,所有放到这里保存。 + // 覆盖保存,只会保留最新一份。(比如在云端将文章移到的新的分组) + coverSaveFeeds(feeds); + coverSaveCategories(categories); + coverFeedCategory(feedCategories); + + KLog.e(" 2 - 同步未读,加星文章的ids"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.sync_article_refs)); + + StreamIds tempStreamIds; + List cloudyRefs; + // 获取未读资源 + tempStreamIds = new StreamIds(); + cloudyRefs = new ArrayList<>(); + do { + tempStreamIds = service.getUnreadRefs(getAuthorization(),"user/" + App.i().getUser().getUserId() + App.CATEGORY_ALL, 10000, true, tempStreamIds.getContinuation()).execute().body(); + cloudyRefs.addAll(tempStreamIds.getIds()); + } while (tempStreamIds.getContinuation() != null); + HashSet unreadRefsList = handleUnreadRefs(cloudyRefs); + + // 获取加星资源 + tempStreamIds = new StreamIds(); + cloudyRefs = new ArrayList<>(); + do { + tempStreamIds = service.getStarredRefs(getAuthorization(),"user/" + App.i().getUser().getUserId() + App.CATEGORY_STARED, 10000, tempStreamIds.getContinuation()).execute().body(); + cloudyRefs.addAll(tempStreamIds.getIds()); + } while (tempStreamIds.getContinuation() != null); + HashSet staredRefsList = handleStaredRefs(cloudyRefs); + + + ArrayList> refsList = splitRefs(unreadRefsList, staredRefsList); + int allSize = refsList.get(0).size() + refsList.get(1).size() + refsList.get(2).size(); + + KLog.e("1 - 同步文章内容"); + + // 抓取【未读、未加星】文章 + fetchArticle(allSize, 0, new ArrayList<>(refsList.get(0)), new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(System.currentTimeMillis()); + article.setReadStatus(App.STATUS_UNREAD); + article.setStarStatus(App.STATUS_UNSTAR); + article.setUid(uid); + return article; + } + }); + // 抓取【已读、已加星】文章 + fetchArticle(allSize, refsList.get(0).size(), new ArrayList<>(refsList.get(1)), new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(System.currentTimeMillis()); + article.setReadStatus(App.STATUS_READED); + article.setStarStatus(App.STATUS_STARED); + article.setUid(uid); + return article; + } + }); + + // 抓取【未读、已加星】文章 + fetchArticle(allSize, refsList.get(0).size() + refsList.get(1).size(), new ArrayList<>(refsList.get(2)), new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(System.currentTimeMillis()); + article.setReadStatus(App.STATUS_UNSTAR); + article.setStarStatus(App.STATUS_STARED); + article.setUid(uid); + return article; + } + }); + + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.clear_article)); + deleteExpiredArticles(); + handleDuplicateArticle(); + handleCrawlDate(); + updateCollectionCount(); + + // 获取文章全文 + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.fetch_article_full_content)); + fetchReadability(uid, startSyncTimeMillis); + + // 为所有新增的加星文章自动生成tag + handleNotTagStarArticles(uid, startSyncTimeMillis); + // 执行文章自动处理脚本 + ArticleActionConfig.i().exeRules(uid,startSyncTimeMillis); + // 清理无文章的tag + //clearNotArticleTags(uid); + + // 提示更新完成 + LiveEventBus.get(SyncWorker.NEW_ARTICLE_NUMBER).post(allSize); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + } catch (HttpException e) { + KLog.e("同步时产生HttpException:" + e.message()); + e.printStackTrace(); + handleException(e); + } catch (ConnectException e) { + KLog.e("同步时产生异常ConnectException"); + e.printStackTrace(); + handleException(e); + } catch (SocketTimeoutException e) { + KLog.e("同步时产生异常SocketTimeoutException"); + e.printStackTrace(); + handleException(e); + } catch (IOException e) { + KLog.e("同步时产生异常IOException"); + e.printStackTrace(); + handleException(e); + } catch (RuntimeException e) { + KLog.e("同步时产生异常RuntimeException"); + e.printStackTrace(); + handleException(e); + } + } + + private void handleException(Exception e) { + if (e instanceof HttpException) { + ToastUtils.show("网络异常:" + e.getMessage()); + } else { + updateCollectionCount(); + } + + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + } + + public void renameTag(String tagId, String targetName, CallbackX cb) { + editTag(tagId, targetName, cb); + } + + private void editTag(@Nullable String tagId, @Nullable String targetName, CallbackX cb) { + EditCollection editCollection = new EditCollection(tagId); + if (!TextUtils.isEmpty(tagId)) { + editCollection.setId(tagId); + } + if (!TextUtils.isEmpty(targetName)) { + editCollection.setLabel(targetName); + } + + service.editCollections(getAuthorization(),editCollection).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, retrofit2.Response> response) { + if (response.isSuccessful()) { + KLog.e("修改成功" + response.body().toString()); + cb.onSuccess(null); + } else { + cb.onFailure(null); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure("修改失败" + t.getMessage()); + KLog.e("修改失败"); + } + }); + } + + @Override + public void addFeed(EditFeed editFeed, CallbackX cb) { + service.editFeed(getAuthorization(),editFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, retrofit2.Response> response) { + if (response.isSuccessful()) { + KLog.e("添加成功" + response.body().toString()); + cb.onSuccess(null); + } else { + cb.onFailure(null); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure(t.getMessage()); + KLog.e("添加失败"); + } + }); + } + + @Override + public void renameFeed(String feedId, String feedTitle, CallbackX cb) { + //editFeed(feedId, renamedTitle, null, cb); + EditFeed editFeed = new EditFeed(feedId); + if (!TextUtils.isEmpty(feedTitle)) { + editFeed.setTitle(feedTitle); + } + + service.editFeed(getAuthorization(),editFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, retrofit2.Response> response) { + if(!response.isSuccessful()){ + cb.onFailure("修改失败"); + }else { + cb.onSuccess(response); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + @Override + public void editFeedCategories(List lastCategoryItems, EditFeed editFeed, CallbackX cb) { + service.editFeed(getAuthorization(),editFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, retrofit2.Response> response) { + if(!response.isSuccessful()){ + cb.onFailure("修改失败"); + }else { + cb.onSuccess(null); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + public void unsubscribeFeed(String feedId, CallbackX cb) { + ArrayList feedIds = new ArrayList<>(); + feedIds.add(feedId); + service.delFeed(getAuthorization(),feedIds).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(retrofit2.Call call, retrofit2.Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( "[]".equals(msg) ){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + + } + }); + } + + private void markArticles(String action, List ids, CallbackX cb) { + MarkerAction markerAction = new MarkerAction(); + markerAction.setAction(action); + markerAction.setType(MarkerAction.TYPE_ENTRIES); + markerAction.setEntryIds(ids); + // 成功不返回信息 + service.markers(getAuthorization(),markerAction).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(retrofit2.Call call, retrofit2.Response response) { + if (response.isSuccessful() ){ + cb.onSuccess(null); + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + cb.onFailure("修改失败:原因未知"); + } + }); + } + + private void markArticle(String action, String articleId, CallbackX cb) { + List ids = new ArrayList(); + ids.add(articleId); + markArticles(action, ids,cb); + } + @Override + public void markArticleListReaded(List articleIds, CallbackX cb) { + markArticles(MarkerAction.MARK_AS_READ, articleIds,cb); + } + + + public void markArticleReaded(String articleId,CallbackX cb) { + KLog.e("标记已读F:" ); + markArticle(MarkerAction.MARK_AS_READ, articleId, cb); + } + + public void markArticleUnread(String articleId,CallbackX cb) { + markArticle(MarkerAction.MARK_AS_UNREAD, articleId, cb); + } + + public void markArticleStared(String articleId,CallbackX cb) { + markArticle(MarkerAction.MARK_AS_SAVED, articleId, cb); + } + + public void markArticleUnstar(String articleId,CallbackX cb) { + markArticle(MarkerAction.MARK_AS_UNSAVED, articleId, cb); + } + + + private void fetchArticle(int allSize, int syncedSize, List subIds, ArticleChanger articleChanger) throws IOException{ + int needFetchCount = subIds.size(); + int hadFetchCount = 0; + + while (needFetchCount > 0) { + int fetchUnit = Math.min(needFetchCount, fetchContentCntForEach); + List items = service.getItemContents(getAuthorization(), subIds.subList(hadFetchCount, hadFetchCount = hadFetchCount + fetchUnit)).execute().body(); + List
tempArticleList = new ArrayList<>(fetchUnit); + for (Entry item : items) { + tempArticleList.add(item.convert(articleChanger)); + } + CoreDB.i().articleDao().insert(tempArticleList); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.sync_article_content, syncedSize = syncedSize + fetchUnit, allSize) ); + needFetchCount = subIds.size() - hadFetchCount; + } + } + + private HashSet handleUnreadRefs(List ids) { + //List
localUnreadArticles = WithDB.i().getArtsUnreadNoOrder(); + List
localUnreadArticles = CoreDB.i().articleDao().getUnreadNoOrder(App.i().getUser().getId()); + Map localUnreadArticlesMap = new ArrayMap<>(localUnreadArticles.size()); + List
changedArticles = new ArrayList<>(); + // 筛选下来,最终要去云端获取内容的未读Refs的集合 + HashSet tempUnreadIds = new HashSet<>(ids.size()); + // 数据量大的一方 + for (Article article : localUnreadArticles) { + localUnreadArticlesMap.put(article.getId(), article); + } + // 数据量小的一方 + Article article; + for (String articleId : ids) { + article = localUnreadArticlesMap.get(articleId); + if (article != null) { + localUnreadArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(App.i().getUser().getId(), articleId); + if (article != null && article.getReadStatus() == App.STATUS_READED) { + article.setReadStatus(App.STATUS_UNREAD); + changedArticles.add(article); + } else { + // 本地无,而云端有,加入要请求的未读资源 + tempUnreadIds.add(articleId); + } + } + } + for (Map.Entry entry : localUnreadArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localUnreadArticlesMap.get(entry.getKey()); + // 本地未读设为已读 + article.setReadStatus(App.STATUS_READED); + changedArticles.add(article); + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempUnreadIds; + } + + private HashSet handleStaredRefs(List streamIds) { +// List
localStarredArticles = WithDB.i().getArtsStared(); + List
localStarredArticles = CoreDB.i().articleDao().getStaredNoOrder(App.i().getUser().getId()); + Map localStarredArticlesMap = new ArrayMap<>(localStarredArticles.size()); + List
changedArticles = new ArrayList<>(); + HashSet tempStarredIds = new HashSet<>(streamIds.size()); + + // 第1步,遍历数据量大的一方A,将其比对项目放入Map中 + for (Article article : localStarredArticles) { + localStarredArticlesMap.put(article.getId(), article); + } + + // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY + Article article; + for (String articleId : streamIds) { + article = localStarredArticlesMap.get(articleId); + if (article != null) { + localStarredArticlesMap.remove(articleId); + } else { +// article = WithDB.i().getArticle(articleId); + article = CoreDB.i().articleDao().getById(App.i().getUser().getId(), articleId); + if (article != null) { + article.setStarStatus(App.STATUS_STARED); + changedArticles.add(article); + } else { + // 本地无,而云远端有,加入要请求的未读资源 + tempStarredIds.add(articleId); + } + } + } + + for (Map.Entry entry : localStarredArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localStarredArticlesMap.get(entry.getKey()); + article.setStarStatus(App.STATUS_UNSTAR); + changedArticles.add(article);// 取消加星 + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempStarredIds; + } + + /** + * 将 未读资源 和 加星资源,去重分为3组 + * + * @param tempUnreadIds + * @param tempStarredIds + * @return + */ + private ArrayList> splitRefs(HashSet tempUnreadIds, HashSet tempStarredIds) { +// KLog.e("【reRefs1】云端未读" + tempUnreadIds.size() + ",云端加星" + tempStarredIds.size()); + int total = Math.min(tempUnreadIds.size(), tempStarredIds.size()); + + HashSet reUnreadUnstarRefs; + HashSet reReadStarredRefs = new HashSet<>(tempStarredIds.size()); + HashSet reUnreadStarredRefs = new HashSet<>(total); + + for (String id : tempStarredIds) { + if (tempUnreadIds.contains(id)) { + tempUnreadIds.remove(id); + reUnreadStarredRefs.add(id); + } else { + reReadStarredRefs.add(id); + } + } + reUnreadUnstarRefs = tempUnreadIds; + + ArrayList> refsList = new ArrayList<>(); + refsList.add(reUnreadUnstarRefs); + refsList.add(reReadStarredRefs); + refsList.add(reUnreadStarredRefs); +// KLog.e("【reRefs2】" + reUnreadUnstarRefs.size() + "--" + reReadStarredRefs.size() + "--" + reUnreadStarredRefs.size()); + return refsList; + } + + private ArrayMap> getFeedArticleNeedReadability(ArrayMap> feedIds, ArrayList
articles) { + if (null == feedIds) { + return new ArrayMap<>(); + } + if (articles != null) { + ArrayList
arrayList; + for (Article article : articles) { + arrayList = feedIds.get(article.getFeedId()); + if (null == arrayList) { + arrayList = new ArrayList
(); + arrayList.add(article); + feedIds.put(article.getFeedId(), arrayList); + } else { + arrayList.add(article); + } + } + } + return feedIds; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/FeedlyService.java b/app/src/main/java/me/wizos/loread/network/api/FeedlyService.java new file mode 100644 index 0000000..61f9018 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/FeedlyService.java @@ -0,0 +1,137 @@ +package me.wizos.loread.network.api; + +import androidx.annotation.NonNull; + +import java.util.List; + +import me.wizos.loread.bean.Token; +import me.wizos.loread.bean.feedly.Collection; +import me.wizos.loread.bean.feedly.Entry; +import me.wizos.loread.bean.feedly.FeedItem; +import me.wizos.loread.bean.feedly.Profile; +import me.wizos.loread.bean.feedly.StreamContents; +import me.wizos.loread.bean.feedly.StreamIds; +import me.wizos.loread.bean.feedly.input.EditCollection; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.bean.feedly.input.MarkerAction; +import me.wizos.loread.bean.search.SearchFeeds; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.HTTP; +import retrofit2.http.Header; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +/** + * Created by Wizos on 2019/4/13. + */ + +public interface FeedlyService { + // Post请求的文本参数则用注解@Field来声明,同时还必须给方法添加注解@FormUrlEncoded来告知Retrofit参数为表单参数,如果只为参数增加@Field注解,而不给方法添加@FormUrlEncoded注解运行时会抛异常。 + @FormUrlEncoded + @POST("auth/token") + Call getAccessToken( + @Field("grant_type") String grantType, + @Field("redirect_uri") String redirectUri, + @Field("client_id") String clientId, + @Field("client_secret") String clientSecret, + @Field("code") String code + ); + + @FormUrlEncoded + @POST("auth/token") + Call refreshingAccessToken( + @Field("grant_type") String grantType, + @Field("refresh_token") String refreshToken, + @Field("client_id") String clientId, + @Field("client_secret") String clientSecret + ); + + @GET("profile") + Call getUserInfo( + @Header("authorization") String authorization + ); + + @GET("collections") + Call> getCollections( + @Header("authorization") String authorization + ); + + @GET("collections") + Call> editCollections( + @Header("authorization") String authorization, + @Body EditCollection editCollection + ); + + @GET("streams/ids") + Call getUnreadRefs( + @Header("authorization") String authorization, + @Query("streamId") String streamId, + @Query("count") int count, + @Query("unreadOnly") boolean unreadOnly, + @Query("continuation") String continuation + ); + + @GET("streams/ids") + Call getStarredRefs( + @Header("authorization") String authorization, + @Query("streamId") String streamId, + @Query("count") int count, + @Query("continuation") String continuation + ); + + @Headers("Accept: application/json") + @POST("entries/.mget") + Call> getItemContents( + @Header("authorization") String authorization, + @NonNull @Body List ids + ); + + + @Headers("Accept: application/json") + @POST("feeds/.mget") + Call> getFeedsMeta(@NonNull @Body List feeds); + + @GET("feeds/{feedId}") + Call getFeedMeta(@Path("feedId") String feedId); + + + @GET("streams/{feedId}/contents") + Call getStreamContent( + @Header("authorization") String authorization, + @Path("feedId") String feedId, @Query("count") int count, @Query("continuation") String continuation); + + @GET("search/feeds") + Call getSearchFeeds(@Query("q") String keyWord, @Query("n") int count); + + @Headers("Accept: application/json") + @POST("subscriptions") + Call> editFeed( + @Header("authorization") String authorization, + @NonNull @Body EditFeed editFeed + ); + + // 使用retrofit进行delete请求时,发现其并不支持向服务器传body。https://www.jianshu.com/p/940fd77961db + //@DELETE("subscriptions/.mdelete") + @Headers("Accept: application/json") + @HTTP(method = "DELETE", path = "subscriptions/.mdelete", hasBody = true) + Call delFeed( + @Header("authorization") String authorization, + @Body List feedIds + ); + + // 成功不返回信息 + // 使用@body标签时不能用@FormUrlEncoded标签,不然会报以下异常 + @Headers("Accept: application/json") + @POST("markers") + Call markers( + @Header("authorization") String authorization, + @NonNull @Body MarkerAction markerAction + ); + +} diff --git a/app/src/main/java/me/wizos/loread/network/api/FeverApi.java b/app/src/main/java/me/wizos/loread/network/api/FeverApi.java new file mode 100644 index 0000000..5df98b0 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/FeverApi.java @@ -0,0 +1,1003 @@ +//package me.wizos.loreadx.net.ApiService; +// +//import android.text.TextUtils; +// +//import androidx.annotation.NonNull; +//import androidx.annotation.Nullable; +//import androidx.collection.ArrayMap; +// +//import com.google.gson.GsonBuilder; +//import com.hjq.toast.ToastUtils; +//import com.lzy.okgo.callback.StringCallback; +//import com.lzy.okgo.exception.HttpException; +//import com.socks.library.KLog; +// +//import org.greenrobot.eventbus.EventBus; +//import org.jsoup.Jsoup; +//import org.jsoup.nodes.Document; +//import org.jsoup.select.Elements; +// +//import java.io.IOException; +//import java.net.ConnectException; +//import java.net.SocketTimeoutException; +//import java.util.ArrayList; +//import java.util.HashSet; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Map; +// +//import me.wizos.loreadx.App; +//import me.wizos.loreadx.R; +//import me.wizos.loreadx.activity.ui.login.LoginResult; +//import me.wizos.loreadx.config.GlobalConfig; +//import me.wizos.loreadx.bean.feedly.CategoryItem; +//import me.wizos.loreadx.bean.feedly.input.EditFeed; +//import me.wizos.loreadx.bean.fever.BaseResponse; +//import me.wizos.loreadx.bean.fever.Feeds; +//import me.wizos.loreadx.bean.fever.Group; +//import me.wizos.loreadx.bean.fever.Groups; +//import me.wizos.loreadx.bean.fever.SavedItemIds; +//import me.wizos.loreadx.bean.fever.UnreadItemIds; +//import me.wizos.loreadx.bean.ttrss.request.GetHeadlines; +//import me.wizos.loreadx.bean.ttrss.request.LoginParam; +//import me.wizos.loreadx.bean.ttrss.request.SubscribeToFeed; +//import me.wizos.loreadx.bean.ttrss.request.UnsubscribeFeed; +//import me.wizos.loreadx.bean.ttrss.request.UpdateArticle; +//import me.wizos.loreadx.bean.ttrss.result.SubscribeToFeedResult; +//import me.wizos.loreadx.bean.ttrss.result.TTRSSArticleItem; +//import me.wizos.loreadx.bean.ttrss.result.TTRSSResponse; +//import me.wizos.loreadx.bean.ttrss.result.UpdateArticleResult; +//import me.wizos.loreadx.content_extractor.Extractor; +//import me.wizos.loreadx.db.WithDB; +//import me.wizos.loreadx.db.Article; +//import me.wizos.loreadx.db.Category; +//import me.wizos.loreadx.db.Feed; +//import me.wizos.loreadx.db.FeedCategory; +//import me.wizos.loreadx.event.Sync; +//import me.wizos.loreadx.net.HttpClientManager; +//import me.wizos.loreadx.net.callback.CallbackX; +//import me.wizos.loreadx.utils.DataUtil; +//import me.wizos.loreadx.utils.StringUtil; +//import me.wizos.loreadx.utils.StringUtils; +//import okhttp3.Call; +//import okhttp3.Callback; +//import okhttp3.Request; +//import retrofit2.Response; +//import retrofit2.Retrofit; +//import retrofit2.converter.gson.GsonConverterFactory; +// +///** +// * Created by Wizos on 2019/2/8. +// */ +// +//public class FeverApi extends AuthApi implements LoginInterface{ +// private FeverService service; +// public static String HOST = ""; +// private String authorization; +// public int fetchContentCntForEach = 20; +// +// public FeverApi() { +// if (TextUtils.isEmpty(HOST)) { +// FeverApi.HOST = App.i().getUser().getHost(); +// } +// Retrofit retrofit = new Retrofit.Builder() +// .baseUrl(FeverApi.HOST) // 设置网络请求的Url地址, 必须以/结尾 +// .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 设置数据解析器 +// .client(HttpClientManager.i().simpleClient()) +// .build(); +// service = retrofit.create(FeverService.class); +// } +// +// public static void setHOST(String HOST) { +// FeverApi.HOST = HOST; +// } +// +// public void setAuthorization(String authorization) { +// this.authorization = authorization; +// } +// +// public LoginResult login(String accountId, String accountPd) throws IOException { +// LoginParam loginParam = new LoginParam(); +// loginParam.setUser(accountId); +// loginParam.setPassword(accountPd); +// String auth = StringUtil.MD5(accountId+":"+accountPd); +// BaseResponse loginResultTTRSSResponse = service.login(auth).execute().body(); +// LoginResult loginResult = new LoginResult(); +// if (loginResultTTRSSResponse!= null && loginResultTTRSSResponse.getAuth() == 1) { +// return loginResult.setSuccess(true).setData(auth); +// } else { +// return loginResult.setSuccess(false).setData("登录失败"); +// } +// } +// +// public void login(String accountId, String accountPd,CallbackX cb){ +// LoginParam loginParam = new LoginParam(); +// loginParam.setUser(accountId); +// loginParam.setPassword(accountPd); +// String auth = StringUtil.MD5(accountId+":"+accountPd); +// service.login(auth).enqueue(new retrofit2.Callback() { +// @Override +// public void onResponse(retrofit2.Call call, Response response) { +// if(response.isSuccessful()){ +// BaseResponse loginResponse = response.body(); +// if( loginResponse != null && !loginResponse.isSuccessful() ){ +// cb.onSuccess(auth); +// return; +// } +// cb.onFailure("登录失败:原因未知" + loginResponse.toString()); +// }else { +// cb.onFailure("登录失败:原因未知" + response.message()); +// } +// } +// +// @Override +// public void onFailure(retrofit2.Call call, Throwable t) { +// cb.onFailure("登录失败:" + t.getMessage()); +// } +// }); +// } +// +// public void fetchUserInfo(CallbackX cb){ +// cb.onFailure("暂时不支持"); +// } +// private long syncTimeMillis; +// +// @Override +// public void sync() { +// App.i().isSyncing = true; +// EventBus.getDefault().post(new Sync(Sync.START)); +// try { +// KLog.e("3 - 同步订阅源信息"); +// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_tag_feed))); +// +// Groups groupsResponse = service.getCategoryItems(authorization).execute().body(); +// if (groupsResponse == null || !groupsResponse.isSuccessful()) { +// throw new HttpException("获取失败"); +// } +// +// Iterator categoryItemsIterator = groupsResponse.getGroups().iterator(); +// Group group; +// Category category; +// FeedCategory feedCategoryTmp; +// String[] feedIds; +// ArrayList categories = new ArrayList<>(); +// ArrayList feedCategories = new ArrayList<>(); +// while (categoryItemsIterator.hasNext()) { +// group = categoryItemsIterator.next(); +// if (group.getId() < 1) { +// continue; +// } +// category = group.getCategry(); +// categories.add(category); +// +// feedIds = group.getFeedIds(); +// if( null == feedIds || feedIds.length == 0 ){ +// continue; +// } +// for (String feedId:feedIds) { +// feedCategoryTmp = new FeedCategory(); +// feedCategoryTmp.setCategoryId(category.getId()); +// feedCategoryTmp.setFeedId(feedId); +// feedCategories.add(feedCategoryTmp); +// } +// } +// +// +// Feeds feedItemsTTRSSResponse = service.getFeeds(authorization).execute().body(); +// if (!feedItemsTTRSSResponse.isSuccessful()) { +// throw new HttpException("获取失败"); +// } +// +// Iterator feedItemsIterator = feedItemsTTRSSResponse.getFeeds().iterator(); +// me.wizos.loreadx.bean.fever.Feed feedItem; +// ArrayList feeds = new ArrayList<>(); +// while (feedItemsIterator.hasNext()) { +// feedItem = feedItemsIterator.next(); +// feeds.add(feedItem.convert()); +// } +// +// // 如果在获取到数据的时候就保存,那么到这里同步断了的话,可能系统内的文章就找不到响应的分组,所有放到这里保存。 +// // 覆盖保存,只会保留最新一份。(比如在云端将文章移到的新的分组) +// WithDB.i().coverSaveFeeds(feeds); +// WithDB.i().coverSaveCategories(categories); +// WithDB.i().coverFeedCategory(feedCategories); +// +// // updateFeedUnreadCount(); +// +// +// +// syncTimeMillis = System.currentTimeMillis(); +// // 获取所有未读的资源 +// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.sync_article_refs))); +// HashSet idRefsSet = new HashSet<>(); +// +// UnreadItemIds unreadItemIdsRes = service.getUnreadItemIds(authorization).execute().body(); +// if( null == unreadItemIdsRes || !unreadItemIdsRes.isSuccessful() ){ +// throw new HttpException("获取文章资源失败"); +// } +// String[] unreadIds = unreadItemIdsRes.getUreadItemIds(); +// if( null != unreadIds && unreadIds.length != 0 ){ +// for (String id:unreadIds) { +// idRefsSet.add(id); +// } +// } +// +// SavedItemIds savedItemIds = service.getSavedItemIds(authorization).execute().body(); +// if( null == savedItemIds || !savedItemIds.isSuccessful() ){ +// throw new HttpException("获取文章资源失败"); +// } +// String[] savedIds = savedItemIds.getSavedItemIds(); +// if( null != savedIds && savedIds.length != 0 ){ +// for (String id:savedIds) { +// idRefsSet.add(id); +// } +// } +// +// +//// ids = new ArrayList<>(refsList.get(0)); +//// needFetchCount = ids.size(); +//// hadFetchCount = 0; +// while (idRefsSet.size() > 0){ +// int needFetchCount = Math.min(idRefsSet.size(),fetchContentCntForEach); +// +// } +// +// +// GetHeadlines getHeadlines = new GetHeadlines(); +// getHeadlines.setSid(authorization); +// +// Article article = WithDB.i().getLastArticle(); +// if (null != article) { +// getHeadlines.setSince_id(article.getId()); +// } +// TTRSSResponse> ttrssArticleItemsResponse; +// Iterator ttrssArticleItemIterator; +// ArrayList
articles; +// TTRSSArticleItem ttrssArticleItem; +// int hadFetchCountUnit, hadFetchCountTotal; +// +// +// List cloudyRefs; +// +// // 获取所有未读的资源 +// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.sync_article_refs))); +// getHeadlines.setShow_content(false); +// getHeadlines.setLimit(200); +// cloudyRefs = new ArrayList<>(); +// hadFetchCountTotal = hadFetchCountUnit = 0; +// do { +// hadFetchCountTotal = hadFetchCountTotal + hadFetchCountUnit; +// getHeadlines.setSkip(hadFetchCountTotal); +// ttrssArticleItemsResponse = service.getHeadlines(getHeadlines).execute().body(); +// if (!ttrssArticleItemsResponse.isSuccessful()) { +// throw new HttpException("获取失败"); +// } +// ttrssArticleItemIterator = ttrssArticleItemsResponse.getData().iterator(); +// hadFetchCountUnit = ttrssArticleItemsResponse.getData().size(); +// while (ttrssArticleItemIterator.hasNext()) { +// cloudyRefs.add(ttrssArticleItemIterator.next().getId()); +// } +// } while (hadFetchCountUnit > 0); +// HashSet unreadRefsSet = handleUnreadRefs(cloudyRefs); +// +// +// // 获取所有加星的资源 +// getHeadlines.setFeed_id("-1"); +// getHeadlines.setView_mode("all_articles"); +// cloudyRefs = new ArrayList<>(); +// hadFetchCountTotal = hadFetchCountUnit = 0; +// do { +// hadFetchCountTotal = hadFetchCountTotal + hadFetchCountUnit; +// getHeadlines.setSkip(hadFetchCountTotal); +// ttrssArticleItemsResponse = service.getHeadlines(getHeadlines).execute().body(); +// if (!ttrssArticleItemsResponse.isSuccessful()) { +// throw new HttpException("获取失败"); +// } +// ttrssArticleItemIterator = ttrssArticleItemsResponse.getData().iterator(); +// hadFetchCountUnit = ttrssArticleItemsResponse.getData().size(); +// while (ttrssArticleItemIterator.hasNext()) { +// cloudyRefs.add(ttrssArticleItemIterator.next().getId()); +// } +// } while (hadFetchCountUnit > 0); +// HashSet staredRefsSet = handleStaredRefs(cloudyRefs); +// +//// ArrayList> refsList = splitRefs(unreadRefsSet, staredRefsSet); +//// int readySyncArtsCapacity = refsList.get(0).size() + refsList.get(1).size() + refsList.get(2).size(); +// +//// List ids; +//// int alreadySyncedArtsNum = 0, hadFetchCount, needFetchCount, num; +//// ArrayList
tempArticleList; +//// ArrayMap> needReadabilityArticles = new ArrayMap>(); +//// +//// ids = new ArrayList<>(refsList.get(0)); +//// needFetchCount = ids.size(); +//// hadFetchCount = 0; +//// +//// KLog.e("1 - 同步文章内容"); +//// // KLog.e("栈的数量A:" + ids.size()); +//// syncTimeMillis = System.currentTimeMillis(); +//// while (needFetchCount > 0) { +//// num = Math.min(needFetchCount, fetchContentCntForEach); +//// getHeadlines.setSkip(alreadySyncedArtsNum); +//// +//// ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num); +//// +//// ttrssArticleItemsResponse = service.getHeadlines(getHeadlines).execute().body(); +//// if (!ttrssArticleItemsResponse.isSuccessful()) { +//// throw new HttpException("获取失败"); +//// } +//// +//// +//// ttrssArticleItemIterator = ttrssArticleItemsResponse.getData().iterator(); +//// hadFetchCountUnit = ttrssArticleItemsResponse.getData().size(); +//// +//// articles = new ArrayList<>(ttrssArticleItemsResponse.getData().size()); +//// +//// while (ttrssArticleItemIterator.hasNext()) { +//// ttrssArticleItem = ttrssArticleItemIterator.next(); +//// articles.add(ttrssArticleItem.convert(unreadArticleChanger)); +//// } +//// classArticles = classArticlesByFeedId(classArticles, articles); +//// WithDB.i().saveArticles(articles); +//// +//// +//// alreadySyncedArtsNum = alreadySyncedArtsNum + num; +//// needFetchCount = ids.size() - hadFetchCount; +//// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_content, alreadySyncedArtsNum, readySyncArtsCapacity))); +//// } +//// +//// do { +//// hadFetchCountTotal = hadFetchCountTotal + hadFetchCountUnit; +//// getHeadlines.setSkip(hadFetchCountTotal); +//// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.sync_article_content, hadFetchCountTotal))); +//// ttrssArticleItemsResponse = service.getHeadlines(getHeadlines).execute().body(); +//// if (!ttrssArticleItemsResponse.isSuccessful()) { +//// throw new HttpException("获取失败"); +//// } +//// ttrssArticleItemIterator = ttrssArticleItemsResponse.getData().iterator(); +//// hadFetchCountUnit = ttrssArticleItemsResponse.getData().size(); +//// +//// articles = new ArrayList<>(ttrssArticleItemsResponse.getData().size()); +//// +//// while (ttrssArticleItemIterator.hasNext()) { +//// ttrssArticleItem = ttrssArticleItemIterator.next(); +//// articles.add(ttrssArticleItem.convert(unreadArticleChanger)); +//// } +//// classArticles = classArticlesByFeedId(classArticles, articles); +//// WithDB.i().saveArticles(articles); +//// } while (hadFetchCountUnit > 0); +//// +//// +//// +//// ids = new ArrayList<>(refsList.get(2)); +//// needFetchCount = ids.size(); +//// hadFetchCount = 0; +//// // KLog.e("栈的数量C:" + ids.size()); +//// while (needFetchCount > 0) { +//// num = Math.min(needFetchCount, fetchContentCntForEach); +//// List entryList = service.getItemContents(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)).execute().body(); +//// tempArticleList = parseItemContents(entryList, new ArticleChanger() { +//// @Override +//// public Article change(Article article) { +//// article.setReadStatus(App.STATUS_UNREAD); +//// article.setStarStatus(App.STATUS_STARED); +//// article.setCrawlDate(syncTimeMillis); +//// return article; +//// } +//// }); +//// WithDB.i().saveArticles(tempArticleList); +//// needReadabilityArticles = classArticlesByFeedId(needReadabilityArticles, tempArticleList); +//// alreadySyncedArtsNum = alreadySyncedArtsNum + num; +//// needFetchCount = ids.size() - hadFetchCount; +//// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_content, alreadySyncedArtsNum, readySyncArtsCapacity))); +//// } +//// +//// ids = new ArrayList<>(refsList.get(1)); +//// needFetchCount = ids.size(); +//// hadFetchCount = 0; +//// // KLog.e("栈的数量B:" + ids.size()); +//// while (needFetchCount > 0) { +//// num = Math.min(needFetchCount, fetchContentCntForEach); +//// List entryList = service.getItemContents(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)).execute().body(); +//// tempArticleList = parseItemContents(entryList, new ArticleChanger() { +//// @Override +//// public Article change(Article article) { +//// article.setReadStatus(App.STATUS_READED); +//// article.setStarStatus(App.STATUS_STARED); +//// article.setCrawlDate(syncTimeMillis); +//// return article; +//// } +//// }); +//// WithDB.i().saveArticles(tempArticleList); +//// needReadabilityArticles = classArticlesByFeedId(needReadabilityArticles, tempArticleList); +//// alreadySyncedArtsNum = alreadySyncedArtsNum + num; +//// needFetchCount = ids.size() - hadFetchCount; +//// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_content, alreadySyncedArtsNum, readySyncArtsCapacity))); +//// } +//// +//// updateFeedUnreadCount(); +//// +//// WithDB.i().handleDuplicateArticle(); +//// +//// // 获取文章全文 +//// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_readability_content))); +//// fetchReadability(needReadabilityArticles); +//// EventBus.getDefault().post(new Sync(Sync.END)); +// +// /////////// +// +// +// KLog.e(" 2 - 同步未读文章"); +// ArrayMap> classArticles = new ArrayMap>(); +// +// +// ArticleChanger unreadArticleChanger = new ArticleChanger() { +// @Override +// public Article change(Article article) { +// article.setCrawlDate(syncTimeMillis); +// return article; +// } +// }; +// hadFetchCountUnit = 0; +// hadFetchCountTotal = 0; +// do { +// hadFetchCountTotal = hadFetchCountTotal + hadFetchCountUnit; +// getHeadlines.setSkip(hadFetchCountTotal); +// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.sync_article_content, hadFetchCountTotal))); +// ttrssArticleItemsResponse = service.getHeadlines(getHeadlines).execute().body(); +// if (!ttrssArticleItemsResponse.isSuccessful()) { +// throw new HttpException("获取失败"); +// } +// ttrssArticleItemIterator = ttrssArticleItemsResponse.getData().iterator(); +// hadFetchCountUnit = ttrssArticleItemsResponse.getData().size(); +// +// articles = new ArrayList<>(ttrssArticleItemsResponse.getData().size()); +// +// while (ttrssArticleItemIterator.hasNext()) { +// ttrssArticleItem = ttrssArticleItemIterator.next(); +// articles.add(ttrssArticleItem.convert(unreadArticleChanger)); +// } +// classArticles = classArticlesByFeedId(classArticles, articles); +// WithDB.i().saveArticles(articles); +// } while (hadFetchCountUnit > 0); +// +// +// getHeadlines.setFeed_id("-1"); +// getHeadlines.setView_mode("all_articles"); +// ArticleChanger staredArticleChanger = new ArticleChanger() { +// @Override +// public Article change(Article article) { +// article.setStarStatus(App.STATUS_STARED); +// article.setCrawlDate(syncTimeMillis); +// return article; +// } +// }; +// int fetchCountTotal = hadFetchCountTotal; +// KLog.e(" 1 - 同步加星文章"); +// hadFetchCountUnit = 0; +// hadFetchCountTotal = 0; +// do { +// hadFetchCountTotal = hadFetchCountTotal + hadFetchCountUnit; +// getHeadlines.setSkip(hadFetchCountTotal); +// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.sync_article_content, fetchCountTotal + hadFetchCountTotal))); +// ttrssArticleItemsResponse = service.getHeadlines(getHeadlines).execute().body(); +// if (!ttrssArticleItemsResponse.isSuccessful()) { +// throw new HttpException("获取失败"); +// } +// ttrssArticleItemIterator = ttrssArticleItemsResponse.getData().iterator(); +// hadFetchCountUnit = ttrssArticleItemsResponse.getData().size(); +// +// articles = new ArrayList<>(ttrssArticleItemsResponse.getData().size()); +// +// while (ttrssArticleItemIterator.hasNext()) { +// ttrssArticleItem = ttrssArticleItemIterator.next(); +// articles.add(ttrssArticleItem.convert(staredArticleChanger)); +// } +// classArticles = classArticlesByFeedId(classArticles, articles); +// WithDB.i().saveArticles(articles); +// } while (hadFetchCountUnit > 0); +// +// updateFeedUnreadCount(); +// +// WithDB.i().handleDuplicateArticle(); +// +// // 获取文章全文 +// EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_readability_content))); +// fetchReadability(classArticles); +// EventBus.getDefault().post(new Sync(Sync.END)); +// } catch (HttpException e) { +// KLog.e("同步时产生HttpException:" + e.message()); +// e.printStackTrace(); +// handleException(e); +// } catch (ConnectException e) { +// KLog.e("同步时产生异常ConnectException"); +// e.printStackTrace(); +// handleException(e); +// } catch (SocketTimeoutException e) { +// KLog.e("同步时产生异常SocketTimeoutException"); +// e.printStackTrace(); +// handleException(e); +// } catch (IOException e) { +// KLog.e("同步时产生异常IOException"); +// e.printStackTrace(); +// handleException(e); +// } catch (RuntimeException e) { +// KLog.e("同步时产生异常RuntimeException"); +// e.printStackTrace(); +// handleException(e); +// } +// App.i().isSyncing = false; +// } +// +// private void handleException(Exception e) { +// if (e instanceof HttpException) { +// ToastUtils.show("网络异常:" + e.getMessage()); +// // EventBus.getDefault().post(new Sync(Sync.NEED_AUTH)); +// } else { +// updateFeedUnreadCount(); +// } +// +// App.i().isSyncing = false; +// EventBus.getDefault().post(new Sync(Sync.ERROR)); +// } +// +// @Override +// public void renameTag(String tagId, String targetName, CallbackX cb) { +// cb.onFailure("暂时不支持"); +// } +// +// public void addFeed(EditFeed editFeed, CallbackX cb) { +// SubscribeToFeed subscribeToFeed = new SubscribeToFeed(); +// subscribeToFeed.setSid(authorization); +// subscribeToFeed.setFeed_url(editFeed.getId().replace("feed/", "")); +// if (editFeed.getCategoryItems() != null && editFeed.getCategoryItems().size() != 0) { +// subscribeToFeed.setCategory_id(editFeed.getCategoryItems().get(0).getId()); +// } +// service.subscribeToFeed(subscribeToFeed).enqueue(new retrofit2.Callback>() { +// @Override +// public void onResponse(retrofit2.Call> call, Response> response) { +// if (response.isSuccessful() && response.body().isSuccessful()) { +// KLog.e("添加成功" + response.body().toString()); +// cb.onSuccess("添加成功"); +// } else { +// cb.onFailure("响应失败"); +// } +// } +// +// @Override +// public void onFailure(retrofit2.Call> call, Throwable t) { +// cb.onFailure("添加失败"); +// KLog.e("添加失败"); +// } +// }); +// } +// +//// public CallWrap addFeed(EditFeed editFeed) { +//// SubscribeToFeed subscribeToFeed = new SubscribeToFeed(); +//// subscribeToFeed.setSid(authorization); +//// subscribeToFeed.setFeed_url(editFeed.getId().replace("feed/", "")); +//// if (editFeed.getCategoryItems() != null && editFeed.getCategoryItems().size() != 0) { +//// subscribeToFeed.setCategory_id(editFeed.getCategoryItems().get(0).getId()); +//// } +//// +//// return new CallWrap<>(service.subscribeToFeed(subscribeToFeed), new CallbackWarp>() { +//// @Override +//// public void onResponse(retrofit2.Call> call, retrofit2.Response> response, retrofit2.Callback callbackResult) { +//// if (response.isSuccessful() && response.body().isSuccessful()) { +//// KLog.e("添加成功" + response.body().toString()); +//// callbackResult.onResponse(call, response); +//// } else { +//// callbackResult.onFailure(call, new RuntimeException("响应失败")); +//// } +//// } +//// +//// @Override +//// public void onFailure(retrofit2.Call> call, Throwable t, retrofit2.Callback callbackResult) { +//// callbackResult.onFailure(call, t); +//// KLog.e("添加失败"); +//// } +//// }); +//// } +// +// @Override +// public void renameFeed(String feedId, String renamedTitle, CallbackX cb) { +// cb.onFailure("暂时不支持"); +// } +// +// /** +// * 订阅,编辑feed +// * +// * @param feedId +// * @param feedTitle +// * @param categoryItems +// * @param cb +// */ +// public void editFeed(@NonNull String feedId, @Nullable String feedTitle, @Nullable ArrayList categoryItems, StringCallback cb) { +// } +// +// +// @Override +// public void editFeedCategories(List lastCategoryItems, EditFeed editFeed,CallbackX cb) { +// cb.onFailure("暂时不支持"); +// } +// +// public void unsubscribeFeed(String feedId,CallbackX cb) { +// UnsubscribeFeed unsubscribeFeed = new UnsubscribeFeed(); +// unsubscribeFeed.setSid(authorization); +// unsubscribeFeed.setFeed_id(Integer.valueOf(feedId)); +// service.unsubscribeFeed(unsubscribeFeed).enqueue(new retrofit2.Callback>() { +// @Override +// public void onResponse(retrofit2.Call> call, Response> response) { +// if(response.isSuccessful() && "OK".equals(response.body().getData().get("status"))){ +// cb.onSuccess("退订成功"); +// }else { +// cb.onFailure("退订失败" + response.body().getData().toString()); +// } +// } +// +// @Override +// public void onFailure(retrofit2.Call> call, Throwable t) { +// cb.onSuccess("退订失败" + t.getMessage()); +// } +// }); +// } +// +// +// private void markArticles(int field, int mode, List ids,CallbackX cb) { +// UpdateArticle updateArticle = new UpdateArticle(); +// updateArticle.setSid(authorization); +// updateArticle.setArticle_ids(StringUtils.join(",", ids)); +// updateArticle.setField(field); +// updateArticle.setMode(mode); +// service.updateArticle(updateArticle).enqueue(new retrofit2.Callback>() { +// @Override +// public void onResponse(retrofit2.Call> call, Response> response) { +// if (response.isSuccessful() ){ +// cb.onSuccess(null); +// }else { +// cb.onFailure("修改失败,原因未知"); +// } +// } +// +// @Override +// public void onFailure(retrofit2.Call> call, Throwable t) { +// cb.onFailure("修改失败,原因未知"); +// } +// }); +// } +// +// private void markArticle(int field, int mode, String articleId,CallbackX cb) { +// UpdateArticle updateArticle = new UpdateArticle(); +// updateArticle.setSid(authorization); +// updateArticle.setArticle_ids(articleId); +// updateArticle.setField(field); +// updateArticle.setMode(mode); +// service.updateArticle(updateArticle).enqueue(new retrofit2.Callback>() { +// @Override +// public void onResponse(retrofit2.Call> call, Response> response) { +// if (response.isSuccessful() ){ +// cb.onSuccess(null); +// }else { +// cb.onFailure("修改失败,原因未知"); +// } +// } +// +// @Override +// public void onFailure(retrofit2.Call> call, Throwable t) { +// cb.onFailure("修改失败,原因未知"); +// } +// }); +// } +// +// public void markArticleListReaded(List articleIds,CallbackX cb) { +// markArticles(2, 0, articleIds, cb); +// } +// +// public void markArticleReaded(String articleId, CallbackX cb) { +// markArticle(2, 0, articleId, cb); +// } +// +// public void markArticleUnread(String articleId, CallbackX cb) { +// markArticle(2, 1, articleId, cb); +// } +// +// public void markArticleStared(String articleId, CallbackX cb) { +// markArticle(0, 1, articleId, cb); +// } +// +// public void markArticleUnstar(String articleId,CallbackX cb) { +// markArticle(0, 0, articleId, cb); +// } +// +//// +//// private retrofit2.Call> markArticles(int field, int mode, List ids) { +//// UpdateArticle updateArticle = new UpdateArticle(); +//// updateArticle.setSid(authorization); +//// updateArticle.setArticle_ids(StringUtils.join(",", ids)); +//// updateArticle.setField(field); +//// updateArticle.setMode(mode); +//// return service.updateArticle(updateArticle); +//// } +//// +//// private retrofit2.Call> markArticle(int field, int mode, String articleId) { +//// UpdateArticle updateArticle = new UpdateArticle(); +//// updateArticle.setSid(authorization); +//// updateArticle.setArticle_ids(articleId); +//// updateArticle.setField(field); +//// updateArticle.setMode(mode); +//// return service.updateArticle(updateArticle); +//// } +//// +//// public retrofit2.Call markArticleListReaded(List articleIds) { +//// return markArticles(2, 0, articleIds); +//// } +//// +//// public CallWrap markArticleReaded(String articleId) { +//// return new CallWrap<>(markArticle(2, 0, articleId), new CallbackWarp() { +//// @Override +//// public void onResponse(retrofit2.Call call, retrofit2.Response response, retrofit2.Callback callbackResult) { +//// if (response.isSuccessful()) { +//// callbackResult.onResponse(call, response); +//// } else { +//// callbackResult.onFailure(call, new RuntimeException("响应失败")); +//// } +//// } +//// +//// @Override +//// public void onFailure(retrofit2.Call call, Throwable t, retrofit2.Callback callbackResult) { +//// callbackResult.onFailure(call, t); +//// } +//// }); +//// } +//// +//// public CallWrap markArticleUnread(String articleId) { +//// return new CallWrap(markArticle(2, 1, articleId), new CallbackWarp() { +//// @Override +//// public void onResponse(retrofit2.Call call, retrofit2.Response response, retrofit2.Callback callbackResult) { +//// if (response.isSuccessful()) { +//// callbackResult.onResponse(call, response); +//// } else { +//// callbackResult.onFailure(call, new RuntimeException("响应失败")); +//// } +//// } +//// +//// @Override +//// public void onFailure(retrofit2.Call call, Throwable t, retrofit2.Callback callbackResult) { +//// callbackResult.onFailure(call, t); +//// } +//// }); +//// } +//// +//// public CallWrap markArticleStared(String articleId) { +//// return new CallWrap(markArticle(0, 1, articleId), new CallbackWarp() { +//// @Override +//// public void onResponse(retrofit2.Call call, retrofit2.Response response, retrofit2.Callback callbackResult) { +//// if (response.isSuccessful()) { +//// callbackResult.onResponse(call, response); +//// } else { +//// callbackResult.onFailure(call, new RuntimeException("响应失败")); +//// } +//// } +//// +//// @Override +//// public void onFailure(retrofit2.Call call, Throwable t, retrofit2.Callback callbackResult) { +//// callbackResult.onFailure(call, t); +//// } +//// }); +//// } +//// +//// public CallWrap markArticleUnstar(String articleId) { +//// return new CallWrap(markArticle(0, 0, articleId), new CallbackWarp() { +//// @Override +//// public void onResponse(retrofit2.Call call, retrofit2.Response response, retrofit2.Callback callbackResult) { +//// if (response.isSuccessful()) { +//// callbackResult.onResponse(call, response); +//// } else { +//// callbackResult.onFailure(call, new RuntimeException("响应失败")); +//// } +//// } +//// +//// @Override +//// public void onFailure(retrofit2.Call call, Throwable t, retrofit2.Callback callbackResult) { +//// callbackResult.onFailure(call, t); +//// } +//// }); +//// } +// +// private HashSet handleUnreadRefs(List ids) { +// List
localUnreadArticles = WithDB.i().getArtsUnreadNoOrder(); +// Map localUnreadArticlesMap = new ArrayMap<>(localUnreadArticles.size()); +// List
changedArticles = new ArrayList<>(); +// // 筛选下来,最终要去云端获取内容的未读Refs的集合 +// HashSet tempUnreadIds = new HashSet<>(ids.size()); +// // 数据量大的一方 +// for (Article article : localUnreadArticles) { +// localUnreadArticlesMap.put(article.getId(), article); +// } +// // 数据量小的一方 +// Article article; +// for (Integer articleId : ids) { +// article = localUnreadArticlesMap.get(articleId+""); +// if (article != null) { +// localUnreadArticlesMap.remove(articleId+""); +// } else { +// article = WithDB.i().getArticle(articleId+""); +// if (article != null && article.getReadStatus() == App.STATUS_READED) { +// article.setReadStatus(App.STATUS_UNREAD); +// changedArticles.add(article); +// } else { +// // 本地无,而云端有,加入要请求的未读资源 +// tempUnreadIds.add(articleId+""); +// } +// } +// } +// for (Map.Entry entry : localUnreadArticlesMap.entrySet()) { +// if (entry.getKey() != null) { +// article = localUnreadArticlesMap.get(entry.getKey()); +// // 本地未读设为已读 +// article.setReadStatus(App.STATUS_READED); +// changedArticles.add(article); +// } +// } +// +// WithDB.i().saveArticles(changedArticles); +// return tempUnreadIds; +// } +// +// private HashSet handleStaredRefs(List streamIds) { +// List
localStarredArticles = WithDB.i().getArtsStared(); +// Map localStarredArticlesMap = new ArrayMap<>(localStarredArticles.size()); +// List
changedArticles = new ArrayList<>(); +// HashSet tempStarredIds = new HashSet<>(streamIds.size()); +// +// // 第1步,遍历数据量大的一方A,将其比对项目放入Map中 +// for (Article article : localStarredArticles) { +// localStarredArticlesMap.put(article.getId(), article); +// } +// +// // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY +// Article article; +// for (Integer articleId : streamIds) { +// article = localStarredArticlesMap.get(articleId+""); +// if (article != null) { +// localStarredArticlesMap.remove(articleId+""); +// } else { +// article = WithDB.i().getArticle(articleId+""); +// if (article != null) { +// article.setStarStatus(App.STATUS_STARED); +// changedArticles.add(article); +// } else { +// // 本地无,而云远端有,加入要请求的未读资源 +// tempStarredIds.add(articleId+""); +// } +// } +// } +// +// for (Map.Entry entry : localStarredArticlesMap.entrySet()) { +// if (entry.getKey() != null) { +// article = localStarredArticlesMap.get(entry.getKey()); +// article.setStarStatus(App.STATUS_UNSTAR); +// changedArticles.add(article);// 取消加星 +// } +// } +// +// WithDB.i().saveArticles(changedArticles); +// return tempStarredIds; +// } +// +// /** +// * 将 未读资源 和 加星资源,去重分为3组 +// * +// * @param tempUnreadIds +// * @param tempStarredIds +// * @return +// */ +// private ArrayList> splitRefs(HashSet tempUnreadIds, HashSet tempStarredIds) { +//// KLog.e("【reRefs1】云端未读" + tempUnreadIds.size() + ",云端加星" + tempStarredIds.size()); +// int total = tempUnreadIds.size() > tempStarredIds.size() ? tempStarredIds.size() : tempUnreadIds.size(); +// +// HashSet reUnreadUnstarRefs; +// HashSet reReadStarredRefs = new HashSet<>(tempStarredIds.size()); +// HashSet reUnreadStarredRefs = new HashSet<>(total); +// +// for (String id : tempStarredIds) { +// if (tempUnreadIds.contains(id)) { +// tempUnreadIds.remove(id); +// reUnreadStarredRefs.add(id); +// } else { +// reReadStarredRefs.add(id); +// } +// } +// reUnreadUnstarRefs = tempUnreadIds; +// +// ArrayList> refsList = new ArrayList<>(); +// refsList.add(reUnreadUnstarRefs); +// refsList.add(reReadStarredRefs); +// refsList.add(reUnreadStarredRefs); +//// KLog.e("【reRefs2】" + reUnreadUnstarRefs.size() + "--" + reReadStarredRefs.size() + "--" + reUnreadStarredRefs.size()); +// return refsList; +// } +// +// private ArrayMap> classArticlesByFeedId(ArrayMap> feedIds, ArrayList
articles) { +// if (null == feedIds) { +// return new ArrayMap>(); +// } +// if (articles != null) { +// ArrayList
arrayList; +// for (Article article : articles) { +// arrayList = feedIds.get(article.getFeedId()); +// if (null == arrayList) { +// arrayList = new ArrayList
(); +// arrayList.add(article); +// feedIds.put(article.getFeedId(), arrayList); +// } else { +// arrayList.add(article); +// } +// } +// } +// return feedIds; +// } +// +// private void fetchReadability(ArrayMap> feedIds) { +// if (null == feedIds) { +// return; +// } +// KLog.e("易读,获取到的订阅源:" + feedIds.size()); +// +// ArrayMap> needReadabilityFeedIds = new ArrayMap>(); +// for (Map.Entry> entry : feedIds.entrySet()) { +// //KLog.e("需要获取易读,Key:" + entry.getKey() + " , " + GlobalConfig.i().getDisplayMode(entry.getKey()) ); +// if (App.DISPLAY_READABILITY.equals(GlobalConfig.i().getDisplayMode(entry.getKey()))) { +// needReadabilityFeedIds.put(entry.getKey(), entry.getValue()); +// } +// } +// +// KLog.e("易读,需要获取的数量:" + needReadabilityFeedIds.entrySet().size()); +// for (Map.Entry> entry : needReadabilityFeedIds.entrySet()) { +// for (final Article article : entry.getValue()) { +// //KLog.e("====获取:" + entry.getKey() + " , " + article.getTitle() + " , " + article.getLink()); +// if (TextUtils.isEmpty(article.getLink())) { +// return; +// } +// //KLog.e("====开始请求" ); +// Request request = new Request.Builder().url(article.getLink()).build(); +// Call call = HttpClientManager.i().simpleClient().newCall(request); +// call.enqueue(new Callback() { +// @Override +// public void onFailure(Call call, IOException e) { +// KLog.e("获取失败"); +// } +// +// @Override +// public void onResponse(Call call, okhttp3.Response response) throws IOException { +// if (!response.isSuccessful()) { +// return; +// } +// //KLog.e("已保存易读"); +// Document doc = Jsoup.parse(response.body().byteStream(), DataUtil.getCharsetFromContentType(response.body().contentType().toString()), article.getLink()); +// String content = Extractor.getData(article.getLink(), doc); +// if (TextUtils.isEmpty(content)) { +// return; +// } +// content = StringUtil.getOptimizedContent(article.getLink(), content); +// //KLog.e("易读:" + content ); +// article.setData(content); +// String summary = StringUtil.getOptimizedSummary(content); +// article.setSummary(summary); +// +// // 获取第1个图片作为封面 +// Elements elements = Jsoup.parseBodyFragment(content).getElementsByTag("img"); +// if (elements.size() > 0) { +// String src = elements.get(0).attr("abs:src"); +// // article.setEnclosure(src); +// article.setImage(src); +// } +// WithDB.i().updateArticle(article); +// } +// }); +// } +// } +// } +// +// +//} diff --git a/app/src/main/java/me/wizos/loread/network/api/FeverService.java b/app/src/main/java/me/wizos/loread/network/api/FeverService.java new file mode 100644 index 0000000..6ddcaae --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/FeverService.java @@ -0,0 +1,108 @@ +//package me.wizos.loreadx.net.ApiService; +// +//import androidx.annotation.NonNull; +// +//import java.util.List; +//import java.util.Map; +// +//import me.wizos.loreadx.bean.fever.BaseResponse; +//import me.wizos.loreadx.bean.fever.Feeds; +//import me.wizos.loreadx.bean.fever.Groups; +//import me.wizos.loreadx.bean.fever.Items; +//import me.wizos.loreadx.bean.fever.SavedItemIds; +//import me.wizos.loreadx.bean.fever.UnreadItemIds; +//import me.wizos.loreadx.bean.ttrss.request.GetFeeds; +//import me.wizos.loreadx.bean.ttrss.request.GetHeadlines; +//import me.wizos.loreadx.bean.ttrss.request.SubscribeToFeed; +//import me.wizos.loreadx.bean.ttrss.request.UnsubscribeFeed; +//import me.wizos.loreadx.bean.ttrss.request.UpdateArticle; +//import me.wizos.loreadx.bean.ttrss.result.SubscribeToFeedResult; +//import me.wizos.loreadx.bean.ttrss.result.TTRSSArticleItem; +//import me.wizos.loreadx.bean.ttrss.result.TTRSSFeedItem; +//import me.wizos.loreadx.bean.ttrss.result.TTRSSResponse; +//import me.wizos.loreadx.bean.ttrss.result.UpdateArticleResult; +//import retrofit2.Call; +//import retrofit2.http.Body; +//import retrofit2.http.Field; +//import retrofit2.http.FormUrlEncoded; +//import retrofit2.http.Headers; +//import retrofit2.http.POST; +//import retrofit2.http.Query; +// +///** +// * Created by Wizos on 2019/11/23. +// */ +// +//public interface FeverService { +// // Post请求的文本参数则用注解@Field来声明,同时还必须给方法添加注解@FormUrlEncoded来告知Retrofit参数为表单参数,如果只为参数增加@Field注解,而不给方法添加@FormUrlEncoded注解运行时会抛异常。 +// @FormUrlEncoded +// @POST("?api") +// Call login( +// @NonNull @Query("api_key") String apiKey +// ); +// @FormUrlEncoded +// @POST("?api&groups") +// Call getCategoryItems( +// //@NonNull @Field("api_key") String apiKey +// @NonNull @Query("api_key") String apiKey +// ); +// @FormUrlEncoded +// @POST("?api&feeds") +// Call getFeeds( +// @NonNull @Query("api_key") String apiKey +// ); +// +// @FormUrlEncoded +// @POST("?api&unread_item_ids") +// Call getUnreadItemIds( +// @NonNull @Query("api_key") String apiKey +// ); +// +// @FormUrlEncoded +// @POST("?api&saved_item_ids") +// Call getSavedItemIds( +// @NonNull @Query("api_key") String apiKey +// ); +// +// +// /** +// * 每次最多 50 条 +// */ +// @FormUrlEncoded +// @POST("?api&items") +// Call getItems( +// @NonNull @Query("api_key") String apiKey, +// @Query("since_id") String sinceId, +// @Query("max_id") String maxId, +// @Query("with_ids") String withIds +// ); +// +// +// +// +// +// @FormUrlEncoded +// @POST("api/") +// Call> updateArticle( +// @NonNull @Body UpdateArticle updateArticle +// ); +// +// +// @Headers("Accept: application/json") +// @POST("api/") +// Call> subscribeToFeed( +// @NonNull @Body SubscribeToFeed subscribeToFeed +// ); +// +// @Headers("Accept: application/json") +// @POST("api/") +// Call> unsubscribeFeed( +// @NonNull @Body UnsubscribeFeed unsubscribeFeed +// ); +// +//// @Headers("Accept: application/json") +//// @POST("subscriptions") +//// Call> editFeed( +//// @NonNull @Body EditFeed editFeed +//// ); +//} diff --git a/app/src/main/java/me/wizos/loread/network/api/InoReaderApi.java b/app/src/main/java/me/wizos/loread/network/api/InoReaderApi.java new file mode 100644 index 0000000..ab3997d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/InoReaderApi.java @@ -0,0 +1,862 @@ +package me.wizos.loread.network.api; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.collection.ArrayMap; + +import com.hjq.toast.ToastUtils; +import com.jeremyliao.liveeventbus.LiveEventBus; +import com.lzy.okgo.exception.HttpException; +import com.socks.library.KLog; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.bean.Token; +import me.wizos.loread.bean.feedly.CategoryItem; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.bean.inoreader.ItemIds; +import me.wizos.loread.bean.inoreader.ItemRefs; +import me.wizos.loread.bean.inoreader.LoginResult; +import me.wizos.loread.bean.inoreader.SubCategories; +import me.wizos.loread.bean.inoreader.Subscription; +import me.wizos.loread.bean.inoreader.UserInfo; +import me.wizos.loread.bean.inoreader.itemContents.Item; +import me.wizos.loread.config.ArticleActionConfig; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.FeedCategory; +import me.wizos.loread.db.User; +import me.wizos.loread.network.HttpClientManager; +import me.wizos.loread.network.StringConverterFactory; +import me.wizos.loread.network.SyncWorker; +import me.wizos.loread.network.callback.CallbackX; +import okhttp3.FormBody; +import okhttp3.RequestBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +import static me.wizos.loread.utils.StringUtils.getString; + +//import okhttp3.Call; + +/** + * 本接口对接 InoReader 服务,从他那获取数据 + * implements LoginInterface + * @author Wizos on 2019/2/15. + */ + +public class InoReaderApi extends OAuthApi implements LoginInterface{ + public static final String APP_ID = "1000001277"; + public static final String APP_KEY = "8dByWzO4AYi425yx5glICKntEY2g3uJo"; + private static String OFFICIAL_BASE_URL = "https://www.inoreader.com"; + private static final String REDIRECT_URI = "loread://oauth_inoreader"; + +// public static final String CLIENTLOGIN = "/accounts/ClientLogin"; +// public static final String USER_INFO = "/reader/api/0/user-info"; +// public static final String ITEM_IDS = "/reader/api/0/stream/items/ids"; // 获取所有文章的id +// public static final String ITEM_CONTENTS = "/reader/api/0/stream/items/contents"; // 获取流的内容 +// public static final String EDIT_TAG = "/reader/api/0/edit-tag"; +// public static final String RENAME_TAG = "/reader/api/0/rename-tag"; +// public static final String EDIT_FEED = "/reader/api/0/subscription/edit"; +// public static final String ADD_FEED = "/reader/api/0/subscription/quickadd"; +// public static final String SUSCRIPTION_LIST = "/reader/api/0/subscription/list"; // 这个不知道现在用在了什么地方 +// public static final String TAG_LIST = "/reader/api/0/tag/list"; +// public static final String STREAM_PREFS = "/reader/api/0/preference/stream/list"; +// public static final String UNREAD_COUNTS = "/reader/api/0/unread-count"; +// public static final String STREAM_CONTENTS = "/reader/api/0/stream/contents/"; +// public static final String Stream_Contents_Atom = "/reader/atom"; +// public static final String Stream_Contents_User = "/reader/api/0/stream/contents/user/"; + // 系统默认的分类 +// public static final String READING_LIST = "/state/com.google/reading-list"; +// public static final String NO_LABEL = "/state/com.google/no-label"; +// public static final String STARRED = "/state/com.google/starred"; +// public static final String UNREAND = "/state/com.google/unread"; + + /* + Code Description + 200 Request OK + 400 Mandatory parameter(s) missing + 401 End-user not authorized + 403 You are not sending the correct AppID and/or AppSecret + 404 Method not implemented + 429 Daily limit reached for this zone + 503 Service unavailable + */ + + private InoReaderService service; + + public InoReaderApi() { + if(App.i().getUser() != null){ + InoReaderApi.OFFICIAL_BASE_URL = App.i().getUser().getHost(); + } + + if (!InoReaderApi.OFFICIAL_BASE_URL.endsWith("/")) { + InoReaderApi.OFFICIAL_BASE_URL = InoReaderApi.OFFICIAL_BASE_URL + "/"; + } + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(InoReaderApi.OFFICIAL_BASE_URL) // 设置网络请求的Url地址, 必须以/结尾 + .addConverterFactory(StringConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 + .client(HttpClientManager.i().inoreaderHttpClient()) + .build(); + service = retrofit.create(InoReaderService.class); + } + + public InoReaderApi(String host) { + if (!TextUtils.isEmpty(host)) { + InoReaderApi.OFFICIAL_BASE_URL = host; + } + + if (!InoReaderApi.OFFICIAL_BASE_URL.endsWith("/")) { + InoReaderApi.OFFICIAL_BASE_URL = InoReaderApi.OFFICIAL_BASE_URL + "/"; + } + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(InoReaderApi.OFFICIAL_BASE_URL) // 设置网络请求的Url地址, 必须以/结尾 + .addConverterFactory(StringConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 + .client(HttpClientManager.i().inoreaderHttpClient()) + .build(); + service = retrofit.create(InoReaderService.class); + } + + public static void setHost(String host) { + InoReaderApi.OFFICIAL_BASE_URL = host; + KLog.i("HOST 地址:" + OFFICIAL_BASE_URL ); + } + + public void login(String accountId, String accountPd,CallbackX cb){ + service.login(accountId, accountPd).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(retrofit2.Call call, Response response) { + if(response.isSuccessful()){ + String result = response.body(); + LoginResult loginResult = new LoginResult(result); + KLog.e("登录结果:" + result + " , " + loginResult.getError() + loginResult.getAuth()); + + if (!loginResult.success) { + cb.onFailure(loginResult.getError()); + }else { + cb.onSuccess(loginResult.getAuth()); + } + }else { + cb.onFailure(response.message()); + } + } + + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + + @Override + public void setAuthorization(String authorization) { + super.setAuthorization(authorization); + } + + public String getOAuthUrl() { +// String baseUrl = HostConfig.i().getRedirectUrl(OFFICIAL_BASE_URL); +// if(StringUtils.isEmpty(baseUrl)){ +// baseUrl = OFFICIAL_BASE_URL; +// } + return OFFICIAL_BASE_URL + "oauth2/auth?response_type=code&client_id=" + APP_ID + "&redirect_uri=" + REDIRECT_URI + "&state=loread&lang=" + Locale.getDefault(); + } + + public void getAccessToken(String authorizationCode,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("grant_type", "authorization_code"); + builder.add("code", authorizationCode); + builder.add("redirect_uri", REDIRECT_URI); + builder.add("client_id", APP_ID); + builder.add("client_secret", APP_KEY); + + service.getAccessToken("authorization_code", REDIRECT_URI,APP_ID,APP_KEY,authorizationCode).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if(response.isSuccessful()){ + cb.onSuccess(response.body()); + }else { + cb.onFailure("失败:" + response.message()); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + cb.onFailure("失败:" + t.getMessage()); + } + }); + } + + public void refreshingAccessToken(String refreshToken, CallbackX cb) { + service.refreshingAccessToken("refresh_token",refreshToken,APP_ID,APP_KEY).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if(response.isSuccessful() && response.body()!=null){ + if (TextUtils.isEmpty(response.body().getRefresh_token())) { + response.body().setRefresh_token(refreshToken); + } + User user = App.i().getUser(); + if (user != null) { + user.setToken(response.body()); + CoreDB.i().userDao().insert(user); + } + // 更新缓存中的授权 + ((FeedlyApi) App.i().getApi()).setAuthorization(App.i().getUser().getAuth()); + + cb.onSuccess(response.body()); + }else { + cb.onFailure("失败:" + response.message()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure("失败:" + t.getMessage()); + } + }); + } + + public String refreshingAccessToken(String refreshToken) throws IOException { + Token token = service.refreshingAccessToken("refresh_token",refreshToken,APP_ID,APP_KEY).execute().body(); + if (TextUtils.isEmpty(token.getRefresh_token())) { + token.setRefresh_token(refreshToken); + } + User user = App.i().getUser(); + if (user != null) { + user.setToken(token); + CoreDB.i().userDao().insert(user); + } + // 更新缓存中的授权 + ((FeedlyApi) App.i().getApi()).setAuthorization(App.i().getUser().getAuth()); + return token.getAuth(); + } + + /** + * 一般用在首次登录的时候,去获取用户基本资料 + * + * @return + * @throws IOException + */ + public void fetchUserInfo( CallbackX cb){ + service.getUserInfo(getAuthorization()).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call,@NonNull Response response) { + KLog.e("获取响应" + getAuthorization() + response.message() ); + KLog.e("获取响应" + response ); + if( response.isSuccessful()){ + KLog.e("获取响应成功" ); + cb.onSuccess(response.body().getUser()); + }else { + KLog.e("获取响应失败"); + cb.onFailure("获取失败:" + response.message()); + } + } + @Override + public void onFailure(@NonNull Call call,@NonNull Throwable t) { + KLog.e("响应失败"); + cb.onFailure("获取失败:" + t.getMessage() + t.toString()); + t.printStackTrace(); + } + }); + } + + @Override + public void sync() { + try { + long startSyncTimeMillis = System.currentTimeMillis(); + String uid = App.i().getUser().getId(); + + KLog.e("3 - 同步订阅源信息"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.sync_feed_info) ); + + // 获取分类 + List categories = service.getCategoryItems(getAuthorization()).execute().body().getCategories(); + // 去除分类中的的一些无用分类 + if (categories != null && categories.size() > 0 && categories.get(0).getId().endsWith("/state/com.google/starred")) { + categories.remove(0); // /state/com.google/starred + } + if (categories != null && categories.size() > 0 && categories.get(0).getId().endsWith("/state/com.google/broadcast")) { + categories.remove(0); // /state/com.google/broadcast + } + if (categories != null && categories.size() > 0 && categories.get(0).getId().endsWith("/state/com.google/blogger-following")) { + categories.remove(0); // /state/com.google/blogger-following + } + String[] array; + String tagTitle; + for (Category category : categories) { + array = category.getId().split("/"); + tagTitle = array[array.length - 1]; + category.setTitle(tagTitle); + } + + // 获取feed + List subscriptions = service.getFeeds(getAuthorization()).execute().body().getSubscriptions(); + List feeds = new ArrayList<>(subscriptions.size()); + Feed feed; + List feedCategories = new ArrayList<>(); + FeedCategory feedCategory; + for (Subscription subscription : subscriptions) { + feed = subscription.convert2Feed(); + feed.setUid(uid); + feeds.add(feed); + for (SubCategories subCategories : subscription.getCategories()) { + feedCategory = new FeedCategory(uid, subscription.getId(), subCategories.getId()); + feedCategories.add(feedCategory); + } + } + + // 如果在获取到数据的时候就保存,那么到这里同步断了的话,可能系统内的文章就找不到响应的分组,所有放到这里保存。 + // (比如在云端将文章移到的新的分组) + coverSaveFeeds(feeds); + coverSaveCategories(categories); + coverFeedCategory(feedCategories); + + KLog.e("2 - 同步文章信息"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.sync_article_refs) ); + // 获取未读资源 + HashSet unreadRefsList = fetchUnreadRefs(); + // 获取加星资源 + HashSet staredRefsList = fetchStaredRefs(); + + KLog.e("1 - 同步文章内容"); + ArrayList> refsList = splitRefs(unreadRefsList, staredRefsList); + int allSize = refsList.get(0).size() + refsList.get(1).size() + refsList.get(2).size(); + + // 抓取【未读、未加星】文章 + fetchArticle(allSize, 0, new ArrayList<>(refsList.get(0)), new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(System.currentTimeMillis()); + article.setReadStatus(App.STATUS_UNREAD); + article.setStarStatus(App.STATUS_UNSTAR); + article.setUid(uid); + return article; + } + }); + // 抓取【已读、已加星】文章 + fetchArticle(allSize, refsList.get(0).size(), new ArrayList<>(refsList.get(1)), new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(System.currentTimeMillis()); + article.setReadStatus(App.STATUS_READED); + article.setStarStatus(App.STATUS_STARED); + article.setUid(uid); + return article; + } + }); + + // 抓取【未读、已加星】文章 + fetchArticle(allSize, refsList.get(0).size() + refsList.get(1).size(), new ArrayList<>(refsList.get(2)), new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(System.currentTimeMillis()); + article.setReadStatus(App.STATUS_UNSTAR); + article.setStarStatus(App.STATUS_STARED); + article.setUid(uid); + return article; + } + }); + + + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.clear_article)); + deleteExpiredArticles(); + + // 获取文章全文 + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.fetch_article_full_content) ); + fetchReadability(uid, startSyncTimeMillis); + + // 为所有新增的加星文章自动生成tag + handleNotTagStarArticles(uid, startSyncTimeMillis); + // 执行文章自动处理脚本 + ArticleActionConfig.i().exeRules(uid,startSyncTimeMillis); + // 清理无文章的tag + //clearNotArticleTags(uid); + + // 提示更新完成 + LiveEventBus.get(SyncWorker.NEW_ARTICLE_NUMBER).post(allSize); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + } catch (IOException e) { + KLog.e("错误"); + e.printStackTrace(); + if (e.getMessage().equals("401")) { + ToastUtils.show("网络异常,请重新登录"); + } + } + + handleDuplicateArticle(); + handleCrawlDate(); + updateCollectionCount(); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + } + + + private void fetchArticle(int allSize, int syncedSize, List subIds, ArticleChanger articleChanger) throws IOException{ + int needFetchCount = subIds.size(); + int hadFetchCount = 0; + + while (needFetchCount > 0) { + int fetchUnit = Math.min(needFetchCount, fetchContentCntForEach); + List items = service.getItemContents(getAuthorization(), genRequestBody(subIds.subList(hadFetchCount, hadFetchCount = hadFetchCount + fetchUnit))).execute().body().getItems(); + List
tempArticleList = new ArrayList<>(fetchUnit); + for (Item item : items) { + tempArticleList.add(item.convert(articleChanger)); + } + CoreDB.i().articleDao().insert(tempArticleList); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.sync_article_content, syncedSize = syncedSize + fetchUnit, allSize) ); + needFetchCount = subIds.size() - hadFetchCount; + } + } + + private HashSet fetchUnreadRefs() throws IOException { + List itemRefs = new ArrayList<>(); + // String info; + ItemIds tempItemIds = new ItemIds(); + int i = 0; + do { + tempItemIds = service.getStreamItemsIds(getAuthorization(),"user/-/state/com.google/reading-list", "user/-/state/com.google/read", 1000, false, tempItemIds.getContinuation()).execute().body(); + i++; + itemRefs.addAll(tempItemIds.getItemRefs()); + } while (tempItemIds.getContinuation() != null && i < 5); + Collections.reverse(itemRefs); // 倒序排列 + + List
localUnreadArticles = CoreDB.i().articleDao().getUnreadNoOrder(App.i().getUser().getId()); + Map localUnreadArticlesMap = new ArrayMap<>(localUnreadArticles.size()); + List
changedArticles = new ArrayList<>(); + // 筛选下来,最终要去云端获取内容的未读Refs的集合 + HashSet tempUnreadIds = new HashSet<>(itemRefs.size()); + // 数据量大的一方 + String articleId; + for (Article article : localUnreadArticles) { + articleId = article.getId(); + localUnreadArticlesMap.put(articleId, article); + } + // 数据量小的一方 + Article article; + for (ItemRefs item : itemRefs) { + articleId = item.getLongId(); + article = localUnreadArticlesMap.get(articleId); + if (article != null) { + localUnreadArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(App.i().getUser().getId(), articleId); + if (article != null && article.getReadStatus() == App.STATUS_READED) { + article.setReadStatus(App.STATUS_UNREAD); + changedArticles.add(article); + } else { + // 本地无,而云端有,加入要请求的未读资源 + tempUnreadIds.add(articleId); + } + } + } + for (Map.Entry entry : localUnreadArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localUnreadArticlesMap.get(entry.getKey()); + // 本地未读设为已读 + article.setReadStatus(App.STATUS_READED); + changedArticles.add(article); + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempUnreadIds; + } + + private HashSet fetchStaredRefs() throws HttpException, IOException { + List itemRefs = new ArrayList<>(); + String info; + ItemIds tempItemIds = new ItemIds(); + int i = 0; + do { + tempItemIds = service.getStreamItemsIds(getAuthorization(),"user/-/state/com.google/starred", null, 1000, false, tempItemIds.getContinuation()).execute().body(); + i++; + itemRefs.addAll(tempItemIds.getItemRefs()); + } while (tempItemIds.getContinuation() != null && i < 5); + Collections.reverse(itemRefs); // 倒序排列 + + List
localStarredArticles = CoreDB.i().articleDao().getStaredNoOrder(App.i().getUser().getId()); + Map localStarredArticlesMap = new ArrayMap<>(localStarredArticles.size()); + List
changedArticles = new ArrayList<>(); + HashSet tempStarredIds = new HashSet<>(itemRefs.size()); + + String articleId; + // 第1步,遍历数据量大的一方A,将其比对项目放入Map中 + for (Article article : localStarredArticles) { + articleId = article.getId(); + localStarredArticlesMap.put(articleId, article); + } + + // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY + Article article; + for (ItemRefs item : itemRefs) { + articleId = item.getLongId(); + article = localStarredArticlesMap.get(articleId); + if (article != null) { + localStarredArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(App.i().getUser().getId(), articleId); + if (article != null) { + article.setStarStatus(App.STATUS_STARED); + changedArticles.add(article); + } else { + // 本地无,而云远端有,加入要请求的未读资源 + tempStarredIds.add(articleId); + } + } + } + + for (Map.Entry entry : localStarredArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localStarredArticlesMap.get(entry.getKey()); + article.setStarStatus(App.STATUS_UNSTAR); + changedArticles.add(article);// 取消加星 + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempStarredIds; + } + + private RequestBody genRequestBody(List ids) { + FormBody.Builder builder = new FormBody.Builder(); + for (String id : ids) { + builder.add("i", id); + } + return builder.build(); + } + + public void renameTag(String sourceTagId, String targetName, CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("s", sourceTagId); + builder.add("dest", targetName); + //WithHttp.i().asyncPost(HOST + "/reader/api/0/rename-tag", builder, authHeaders, cb); + service.renameTag(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if( response.isSuccessful()){ + String msg = response.body(); + if("OK".equals(msg)){ + cb.onSuccess("修改成功"); + }else if( msg.contains("Tag not found!")){ + cb.onFailure("修改失败:要修改分类不存在"); + } + }else { + cb.onFailure("修改失败:未知原因"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure("修改失败:" + t.getMessage()); + } + }); + } + + public void unsubscribeFeed(String feedId,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("ac", "unsubscribe"); + builder.add("s", feedId); + service.editFeed(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.contains("OK")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + public void addFeed(@NonNull EditFeed editFeed, CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("ac", "subscribe"); + builder.add("s", editFeed.getId()); + List categoryItemList = editFeed.getCategoryItems(); + for (CategoryItem categoryItem : categoryItemList) { + builder.add("a", categoryItem.getId()); + } + service.editFeed(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + KLog.e("添加成功" + response.body().toString()); + cb.onSuccess("添加成功"); + } else { + cb.onFailure("响应失败"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + public void renameFeed(String feedId, String renamedTitle, CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); +// builder.add("ac", "edit"); // 可省略 + builder.add("s", feedId); + builder.add("t", renamedTitle); + service.editFeed(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.contains("OK")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + @Override + public void editFeedCategories(List lastCategoryItems, EditFeed editFeed, CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("ac", "edit"); + builder.add("s", editFeed.getId()); + + ArrayList selectedCategoryItems = editFeed.getCategoryItems(); + ArrayMap lastCategoryItemsMap = new ArrayMap<>(lastCategoryItems.size()); + for (CategoryItem categoryItem : lastCategoryItems) { + lastCategoryItemsMap.put(categoryItem.getId(), categoryItem); + } + for (CategoryItem categoryItem : selectedCategoryItems) { + if (lastCategoryItemsMap.get(categoryItem.getId()) == null) { + builder.add("a", categoryItem.getId()); + lastCategoryItemsMap.remove(categoryItem); + } + } + for (Map.Entry entry : lastCategoryItemsMap.entrySet()) { + builder.add("r", entry.getKey()); + } + //WithHttp.i().asyncPost(HOST + "/reader/api/0/subscription/edit", builder, authHeaders, cb); + service.editFeed(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.equalsIgnoreCase("ok")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + + public void markArticleListReaded(List articleIDs,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("a", "user/-/state/com.google/read"); + for (String articleID : articleIDs) { + builder.add("i", articleID); + } + service.markArticle(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.equalsIgnoreCase("ok")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + @Override + public void markArticleReaded(String articleID, CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("a", "user/-/state/com.google/read"); + builder.add("i", articleID); + service.markArticle(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.equalsIgnoreCase("ok")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + public void markArticleUnread(String articleID,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("r", "user/-/state/com.google/read"); + builder.add("i", articleID); + service.markArticle(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.equalsIgnoreCase("ok")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + + public void markArticleStared(String articleID,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("a", "user/-/state/com.google/starred"); + builder.add("i", articleID); + service.markArticle(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.equalsIgnoreCase("ok")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + + public void markArticleUnstar(String articleID,CallbackX cb) { + FormBody.Builder builder = new FormBody.Builder(); + builder.add("r", "user/-/state/com.google/starred"); + builder.add("i", articleID); + service.markArticle(getAuthorization(), builder.build()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() ){ + String msg = response.body(); + if( !TextUtils.isEmpty(msg) && msg.equalsIgnoreCase("ok")){ + cb.onSuccess(null); + }else { + cb.onFailure(msg); + } + }else { + cb.onFailure("修改失败:原因未知"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + /** + * 将 未读资源 和 加星资源,去重分为3组 + * + * @param tempUnreadIds + * @param tempStarredIds + * @return + */ + public ArrayList> splitRefs(HashSet tempUnreadIds, HashSet tempStarredIds) { +// KLog.e("【reRefs1】云端未读" + tempUnreadIds.size() + ",云端加星" + tempStarredIds.size()); + int total = Math.min(tempUnreadIds.size(), tempStarredIds.size()); + + HashSet reUnreadUnstarRefs; + HashSet reReadStarredRefs = new HashSet<>(tempStarredIds.size()); + HashSet reUnreadStarredRefs = new HashSet<>(total); + + for (String id : tempStarredIds) { + if (tempUnreadIds.contains(id)) { + tempUnreadIds.remove(id); + reUnreadStarredRefs.add(id); + } else { + reReadStarredRefs.add(id); + } + } + reUnreadUnstarRefs = tempUnreadIds; + + ArrayList> refsList = new ArrayList<>(); + refsList.add(reUnreadUnstarRefs); + refsList.add(reReadStarredRefs); + refsList.add(reUnreadStarredRefs); +// KLog.e("【reRefs2】" + reUnreadUnstarRefs.size() + "--" + reReadStarredRefs.size() + "--" + reUnreadStarredRefs.size()); + return refsList; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/InoReaderService.java b/app/src/main/java/me/wizos/loread/network/api/InoReaderService.java new file mode 100644 index 0000000..9b4b547 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/InoReaderService.java @@ -0,0 +1,132 @@ +package me.wizos.loread.network.api; + +import androidx.annotation.NonNull; + +import me.wizos.loread.bean.Token; +import me.wizos.loread.bean.inoreader.GsTags; +import me.wizos.loread.bean.inoreader.ItemIds; +import me.wizos.loread.bean.inoreader.StreamContents; +import me.wizos.loread.bean.inoreader.Subscriptions; +import me.wizos.loread.bean.inoreader.UserInfo; +import okhttp3.RequestBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.POST; +import retrofit2.http.Query; + +/** + * Created by Wizos on 2019/5/12. + */ + +public interface InoReaderService { + @FormUrlEncoded + @POST("oauth2/token") + Call getAccessToken( + @Field("grant_type") String grantType, + @Field("redirect_uri") String redirectUri, + @Field("client_id") String clientId, + @Field("client_secret") String clientSecret, + @Field("code") String code + ); + + @FormUrlEncoded + @POST("oauth2/token") + Call refreshingAccessToken( + @Field("grant_type") String grantType, + @Field("refresh_token") String refreshToken, + @Field("client_id") String clientId, + @Field("client_secret") String clientSecret + ); + + + @FormUrlEncoded + @POST("accounts/ClientLogin") + Call login( + @Field("Email") String email, + @Field("Passwd") String password + ); + + @GET("/reader/api/0/user-info") + Call getUserInfo( + @Header("authorization") String authorization + ); + + + @GET("reader/api/0/tag/list") + Call getCategoryItems( + @Header("authorization") String authorization + ); + + @GET("reader/api/0/subscription/list") + Call getFeeds( + @Header("authorization") String authorization + ); + + @GET("reader/api/0/stream/items/ids") + Call getStreamItemsIds( + @Header("authorization") String authorization, + @Query("s") String s, + @Query("xt") String xt, + @Query("n") int count, + @Query("includeAllDirectStreamIds") boolean includeAllDirectStreamIds, + @Query("continuation") String continuation + ); + + @GET("reader/api/0/stream/items/ids") + Call getUnreadRefs( + @Header("authorization") String authorization, + @Query("s") String s, + @Query("xt") String xt, + @Query("n") int count, + @Query("includeAllDirectStreamIds") boolean includeAllDirectStreamIds, + @Query("continuation") String continuation + ); + + @GET("reader/api/0/stream/items/ids") + Call syncStarredRefs( + @Header("authorization") String authorization, + @Query("s") String s, + @Query("xt") String xt, + @Query("n") int count, + @Query("includeAllDirectStreamIds") boolean includeAllDirectStreamIds, + @Query("continuation") String continuation + ); + + @POST("reader/api/0/stream/items/contents") + Call getItemContents( + @Header("authorization") String authorization, + @NonNull @Body RequestBody requestBody + ); + + + @POST("reader/api/0/subscription/quickadd") + Call addFeed( + @NonNull @Body RequestBody requestBody + ); + + @POST("reader/api/0/subscription/edit") + Call editFeed( + @Header("authorization") String authorization, + @NonNull @Body RequestBody requestBody + ); + + + @POST("reader/api/0/edit-tag") + Call markArticle( + @Header("authorization") String authorization, + @Body RequestBody requestBody + ); + + // 失败返回:Error=Tag not found! + // 成功返回:OK + @POST("reader/api/0/rename-tag") + Call renameTag( + @Header("authorization") String authorization, + @Body RequestBody requestBody + ); + +} diff --git a/app/src/main/java/me/wizos/loread/network/api/LoginInterface.java b/app/src/main/java/me/wizos/loread/network/api/LoginInterface.java new file mode 100644 index 0000000..5c70ed5 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/LoginInterface.java @@ -0,0 +1,6 @@ +package me.wizos.loread.network.api; +import me.wizos.loread.network.callback.CallbackX; + +public interface LoginInterface { + void login(String accountId, String accountPd, CallbackX cb); +} diff --git a/app/src/main/java/me/wizos/loread/network/api/LoreadApi.java b/app/src/main/java/me/wizos/loread/network/api/LoreadApi.java new file mode 100644 index 0000000..538f14b --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/LoreadApi.java @@ -0,0 +1,536 @@ +package me.wizos.loread.network.api; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArrayMap; + +import com.google.gson.GsonBuilder; +import com.hjq.toast.ToastUtils; +import com.jeremyliao.liveeventbus.LiveEventBus; +import com.lzy.okgo.callback.StringCallback; +import com.lzy.okgo.exception.HttpException; +import com.socks.library.KLog; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.activity.login.LoginResult; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.bean.ttrss.request.GetArticles; +import me.wizos.loread.bean.ttrss.request.GetCategories; +import me.wizos.loread.bean.ttrss.request.GetFeeds; +import me.wizos.loread.bean.ttrss.request.GetHeadlines; +import me.wizos.loread.bean.ttrss.request.GetSavedItemIds; +import me.wizos.loread.bean.ttrss.request.GetUnreadItemIds; +import me.wizos.loread.bean.ttrss.request.LoginParam; +import me.wizos.loread.bean.ttrss.request.SubscribeToFeed; +import me.wizos.loread.bean.ttrss.request.UnsubscribeFeed; +import me.wizos.loread.bean.ttrss.request.UpdateArticle; +import me.wizos.loread.bean.ttrss.result.ArticleItem; +import me.wizos.loread.bean.ttrss.result.CategoryItem; +import me.wizos.loread.bean.ttrss.result.FeedItem; +import me.wizos.loread.bean.ttrss.result.SubscribeToFeedResult; +import me.wizos.loread.bean.ttrss.result.TTRSSLoginResult; +import me.wizos.loread.bean.ttrss.result.TinyResponse; +import me.wizos.loread.bean.ttrss.result.UpdateArticleResult; +import me.wizos.loread.config.ArticleActionConfig; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.FeedCategory; +import me.wizos.loread.network.HttpClientManager; +import me.wizos.loread.network.SyncWorker; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.StringUtils; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +import static me.wizos.loread.utils.StringUtils.getString; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class LoreadApi extends AuthApi implements LoginInterface{ + private LoreadService service; + private static String HOST = ""; + + public LoreadApi() { + if (TextUtils.isEmpty(HOST)) { + LoreadApi.HOST = App.i().getUser().getHost(); + KLog.e("HOST 地址:" + HOST ); + } + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(LoreadApi.HOST) // 设置网络请求的Url地址, 必须以/结尾 + .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 设置数据解析器 + .client(HttpClientManager.i().loreadHttpClient()) + .build(); + service = retrofit.create(LoreadService.class); + } + + public static void setHost(String host) { + LoreadApi.HOST = host; + KLog.e("HOST 地址:" + host ); + } + + public LoginResult login(String accountId, String accountPd) throws IOException { + LoginParam loginParam = new LoginParam(); + loginParam.setUser(accountId); + loginParam.setPassword(accountPd); + TinyResponse loginResultTTRSSResponse = service.login(loginParam).execute().body(); + LoginResult loginResult = new LoginResult(); + if (loginResultTTRSSResponse.isSuccessful()) { + return loginResult.setSuccess(true).setData(loginResultTTRSSResponse.getContent().getSession_id()); + } else { + return loginResult.setSuccess(false).setData(loginResultTTRSSResponse.getContent().getSession_id()); + } + } + + public void login(String accountId, String accountPd,CallbackX cb){ + LoginParam loginParam = new LoginParam(); + loginParam.setUser(accountId); + loginParam.setPassword(accountPd); + service.login(loginParam).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, Response> response) { + if(response.isSuccessful()){ + TinyResponse loginResultTTRSSResponse = response.body(); + if( loginResultTTRSSResponse.isSuccessful()){ + cb.onSuccess(loginResultTTRSSResponse.getContent().getSession_id()); + return; + } + cb.onFailure("登录失败:原因未知" + loginResultTTRSSResponse.toString()); + }else { + cb.onFailure("登录失败:原因未知" + response.message()); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure("登录失败:" + t.getMessage()); + } + }); + } + + public void fetchUserInfo(CallbackX cb){ + cb.onFailure("暂时不支持"); + } + + @Override + public void sync() { + try { + long startSyncTimeMillis = System.currentTimeMillis(); + String uid = App.i().getUser().getId(); + + KLog.e("3 - 同步订阅源信息:获取分类"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.sync_feed_info)); + + // 获取分类 + TinyResponse> categoryItemsTTRSSResponse = service.getCategories(getAuthorization(),new GetCategories(getAuthorization())).execute().body(); + KLog.e("获取回应:" + categoryItemsTTRSSResponse); + if (!categoryItemsTTRSSResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + + Iterator categoryItemsIterator = categoryItemsTTRSSResponse.getContent().iterator(); + CategoryItem TTRSSCategoryItem; + ArrayList categories = new ArrayList<>(); + while (categoryItemsIterator.hasNext()) { + TTRSSCategoryItem = categoryItemsIterator.next(); + if (Integer.parseInt(TTRSSCategoryItem.getId()) < 1) { + continue; + } + categories.add(TTRSSCategoryItem.convert()); + } + + // 获取feed + TinyResponse> feedItemsTTRSSResponse = service.getFeeds(getAuthorization(),new GetFeeds(getAuthorization())).execute().body(); + if (!feedItemsTTRSSResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + + Iterator feedItemsIterator = feedItemsTTRSSResponse.getContent().iterator(); + FeedItem ttrssFeedItem; + ArrayList feeds = new ArrayList<>(); + ArrayList feedCategories = new ArrayList<>(feedItemsTTRSSResponse.getContent().size()); + FeedCategory feedCategoryTmp; + while (feedItemsIterator.hasNext()) { + ttrssFeedItem = feedItemsIterator.next(); + Feed feed = ttrssFeedItem.convert2Feed(); + feed.setUid(uid); + feeds.add(feed); + feedCategoryTmp = new FeedCategory(uid, String.valueOf(ttrssFeedItem.getId()), String.valueOf(ttrssFeedItem.getCatId())); + feedCategories.add(feedCategoryTmp); + } + + // 如果在获取到数据的时候就保存,那么到这里同步断了的话,可能系统内的文章就找不到响应的分组,所有放到这里保存。 + // 覆盖保存,只会保留最新一份。(比如在云端将文章移到的新的分组) + coverSaveFeeds(feeds); + coverSaveCategories(categories); + coverFeedCategory(feedCategories); + + // 获取所有未读的资源 + KLog.e("2 - 同步文章信息"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.sync_article_refs)); + + GetHeadlines getHeadlines = new GetHeadlines(); + getHeadlines.setSid(getAuthorization()); + Article article = CoreDB.i().articleDao().getLastArticle(uid); + if (null != article) { + getHeadlines.setSince_id(article.getId()); + } + + + TinyResponse idsResponse; + // 获取未读资源 + idsResponse = service.getUnreadItemIds(getAuthorization(),new GetUnreadItemIds(getAuthorization())).execute().body(); + if (!idsResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + KLog.e("未读" + idsResponse.getContent()); + HashSet unreadRefsSet = handleUnreadRefs( idsResponse.getContent().split(",") ); + + // 获取加星资源 + GetSavedItemIds getSavedItemIds = new GetSavedItemIds(getAuthorization()); + idsResponse = service.getSavedItemIds(getAuthorization(),getSavedItemIds).execute().body(); + if (!idsResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + HashSet staredRefsSet = handleStaredRefs( idsResponse.getContent().split(",") ); + + + HashSet idRefsSet = new HashSet<>(); + idRefsSet.addAll(unreadRefsSet); + idRefsSet.addAll(staredRefsSet); + + KLog.i("文章id资源:" + idRefsSet ); + ArrayList ids = new ArrayList<>(idRefsSet); + + int hadFetchCount, needFetchCount, num; + ArrayMap> classArticlesMap = new ArrayMap>(); + + needFetchCount = ids.size(); + hadFetchCount = 0; + + GetArticles getArticles = new GetArticles(getAuthorization()); + KLog.e("1 - 同步文章内容" + needFetchCount + " " ); + + TinyResponse> ttrssArticleItemsResponse; + ArrayList
articles; + + while (needFetchCount > 0) { + num = Math.min(needFetchCount, fetchContentCntForEach); + getArticles.setArticleIds( ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num) ); + ttrssArticleItemsResponse = service.getArticles(getAuthorization(),getArticles).execute().body(); + if (!ttrssArticleItemsResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + List items = ttrssArticleItemsResponse.getContent(); + articles = new ArrayList<>(items.size()); + long syncTimeMillis = System.currentTimeMillis(); + for (ArticleItem item : items) { + articles.add(item.convert(new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(syncTimeMillis); + article.setUid(uid); + return article; + } + })); + } + + CoreDB.i().articleDao().insert(articles); + needFetchCount = ids.size() - hadFetchCount; + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.sync_article_content, hadFetchCount, ids.size()) ); + } + + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.clear_article)); + deleteExpiredArticles(); + handleDuplicateArticle(); + handleCrawlDate(); + updateCollectionCount(); + + // 获取文章全文 + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.fetch_article_full_content)); + fetchReadability(uid, startSyncTimeMillis); + + // 为所有新增的加星文章自动生成tag + handleNotTagStarArticles(uid, startSyncTimeMillis); + // 执行文章自动处理脚本 + ArticleActionConfig.i().exeRules(uid,startSyncTimeMillis); + // 清理无文章的tag + //clearNotArticleTags(uid); + + // 提示更新完成 + LiveEventBus.get(SyncWorker.NEW_ARTICLE_NUMBER).post(ids.size()); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + }catch (IllegalStateException e){ + KLog.e("同步时产生IllegalStateException:" + e.getMessage()); + e.printStackTrace(); + handleException( "同步时产生IllegalStateException:" + e.getMessage() ); + }catch (HttpException e) { + KLog.e("同步时产生HttpException:" + e.message()); + e.printStackTrace(); + handleException("同步时产生HttpException:" + e.message()); + } catch (ConnectException e) { + KLog.e("同步时产生异常ConnectException"); + e.printStackTrace(); + handleException("同步时产生异常ConnectException"); + } catch (SocketTimeoutException e) { + KLog.e("同步时产生异常SocketTimeoutException"); + e.printStackTrace(); + handleException("同步时产生异常SocketTimeoutException"); + } catch (IOException e) { + KLog.e("同步时产生异常IOException"); + e.printStackTrace(); + handleException("同步时产生异常IOException"); + } catch (RuntimeException e) { + KLog.e("同步时产生异常RuntimeException"); + e.printStackTrace(); + handleException("同步时产生异常RuntimeException"); + } + } + + private void handleException(String msg) { + ToastUtils.show(msg); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + } + + @Override + public void renameTag(String tagId, String targetName, CallbackX cb) { + cb.onFailure("暂时不支持"); + } + + public void addFeed(EditFeed editFeed, CallbackX cb) { + SubscribeToFeed subscribeToFeed = new SubscribeToFeed(getAuthorization()); + subscribeToFeed.setFeed_url(editFeed.getId()); + if (editFeed.getCategoryItems() != null && editFeed.getCategoryItems().size() != 0) { + subscribeToFeed.setCategory_id(editFeed.getCategoryItems().get(0).getId()); + } + service.subscribeToFeed(getAuthorization(),subscribeToFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(@NotNull retrofit2.Call> call, @NotNull Response> response) { + if (response.isSuccessful() && response.body().isSuccessful()) { + KLog.e("添加成功" + response.body().toString()); + cb.onSuccess("添加成功"); + } else { + cb.onFailure("响应失败"); + } + } + + @Override + public void onFailure(@NotNull retrofit2.Call> call, @NotNull Throwable t) { + cb.onFailure("添加失败"); + KLog.e("添加失败"); + } + }); + } + + @Override + public void renameFeed(String feedId, String renamedTitle, CallbackX cb) { + cb.onFailure("暂时不支持"); + } + + /** + * 订阅,编辑feed + * + * @param feedId + * @param feedTitle + * @param categoryItems + * @param cb + */ + public void editFeed(@NonNull String feedId, @Nullable String feedTitle, @Nullable ArrayList categoryItems, StringCallback cb) { + } + + + @Override + public void editFeedCategories(List lastCategoryItems, EditFeed editFeed, CallbackX cb) { + cb.onFailure("暂时不支持"); + } + + public void unsubscribeFeed(String feedId,CallbackX cb) { + UnsubscribeFeed unsubscribeFeed = new UnsubscribeFeed(getAuthorization()); + unsubscribeFeed.setFeedId(Integer.parseInt(feedId)); + service.unsubscribeFeed(getAuthorization(),unsubscribeFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(@NotNull retrofit2.Call> call, @NotNull Response> response) { + if(response.isSuccessful() && null != response.body() && null != response.body().getContent() && "OK".equals(response.body().getContent().get("status"))){ + cb.onSuccess("退订成功"); + }else { + cb.onFailure("退订失败" + response.body()); + } + } + + @Override + public void onFailure(@NotNull retrofit2.Call> call, @NotNull Throwable t) { + cb.onSuccess("退订失败" + t.getMessage()); + } + }); + } + + + private void markArticles(int field, int mode, List ids,CallbackX cb) { + UpdateArticle updateArticle = new UpdateArticle(getAuthorization()); + updateArticle.setArticle_ids(StringUtils.join(",", ids)); + updateArticle.setField(field); + updateArticle.setMode(mode); + service.updateArticle(getAuthorization(),updateArticle).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(@NotNull retrofit2.Call> call, @NotNull Response> response) { + if (response.isSuccessful() ){ + cb.onSuccess(null); + }else { + cb.onFailure("修改失败,原因未知"); + } + } + + @Override + public void onFailure(@NotNull retrofit2.Call> call, @NotNull Throwable t) { + cb.onFailure("修改失败,原因未知"); + } + }); + } + + private void markArticle(int field, int mode, String articleId,CallbackX cb) { + UpdateArticle updateArticle = new UpdateArticle(getAuthorization()); + updateArticle.setArticle_ids(articleId); + updateArticle.setField(field); + updateArticle.setMode(mode); + service.updateArticle(getAuthorization(),updateArticle).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(@NotNull retrofit2.Call> call, @NotNull Response> response) { + if (response.isSuccessful() ){ + cb.onSuccess(null); + }else { + cb.onFailure("修改失败,原因未知"); + } + } + + @Override + public void onFailure(@NotNull retrofit2.Call> call, @NotNull Throwable t) { + cb.onFailure("修改失败,原因未知"); + } + }); + } + + public void markArticleListReaded(List articleIds,CallbackX cb) { + markArticles(2, 0, articleIds, cb); + } + + public void markArticleReaded(String articleId, CallbackX cb) { + markArticle(2, 0, articleId, cb); + } + + public void markArticleUnread(String articleId, CallbackX cb) { + markArticle(2, 1, articleId, cb); + } + + public void markArticleStared(String articleId, CallbackX cb) { + markArticle(0, 1, articleId, cb); + } + + public void markArticleUnstar(String articleId,CallbackX cb) { + markArticle(0, 0, articleId, cb); + } + + private HashSet handleUnreadRefs(String[] ids) { + KLog.i("处理未读资源:" + ids.length ); + String uid = App.i().getUser().getId(); + List
localUnreadArticles = CoreDB.i().articleDao().getUnreadNoOrder(uid); + + Map localUnreadArticlesMap = new ArrayMap<>(localUnreadArticles.size()); + List
changedArticles = new ArrayList<>(); + // 筛选下来,最终要去云端获取内容的未读Refs的集合 + HashSet tempUnreadIds = new HashSet<>(ids.length); + // 数据量大的一方 + for (Article article : localUnreadArticles) { + localUnreadArticlesMap.put(article.getId(), article); + } + // 数据量小的一方 + Article article; + for (String articleId : ids) { + article = localUnreadArticlesMap.get(articleId); + if (article != null) { + localUnreadArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(uid,articleId); + if (article != null && article.getReadStatus() == App.STATUS_READED) { + article.setReadStatus(App.STATUS_UNREAD); + changedArticles.add(article); + } else { + // 本地无,而云端有,加入要请求的未读资源 + tempUnreadIds.add(articleId+""); + } + } + } + for (Map.Entry entry : localUnreadArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localUnreadArticlesMap.get(entry.getKey()); + // 本地未读设为已读 + article.setReadStatus(App.STATUS_READED); + changedArticles.add(article); + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempUnreadIds; + } + + private HashSet handleStaredRefs(String[] ids) { + KLog.i("处理加薪资源:" + ids.length); + String uid = App.i().getUser().getId(); + List
localStarredArticles = CoreDB.i().articleDao().getStaredNoOrder(uid); + + Map localStarredArticlesMap = new ArrayMap<>(localStarredArticles.size()); + List
changedArticles = new ArrayList<>(); + HashSet tempStarredIds = new HashSet<>(ids.length); + + // 第1步,遍历数据量大的一方A,将其比对项目放入Map中 + for (Article article : localStarredArticles) { + localStarredArticlesMap.put(article.getId(), article); + } + + // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY + Article article; + for (String articleId : ids) { + article = localStarredArticlesMap.get(articleId); + if (article != null) { + localStarredArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(uid,articleId); + if (article != null) { + article.setStarStatus(App.STATUS_STARED); + changedArticles.add(article); + } else { + // 本地无,而云远端有,加入要请求的未读资源 + tempStarredIds.add(articleId); + } + } + } + + for (Map.Entry entry : localStarredArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localStarredArticlesMap.get(entry.getKey()); + article.setStarStatus(App.STATUS_UNSTAR); + changedArticles.add(article);// 取消加星 + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempStarredIds; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/LoreadService.java b/app/src/main/java/me/wizos/loread/network/api/LoreadService.java new file mode 100644 index 0000000..eb62613 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/LoreadService.java @@ -0,0 +1,110 @@ +package me.wizos.loread.network.api; + +import androidx.annotation.NonNull; + +import java.util.List; +import java.util.Map; + +import me.wizos.loread.bean.ttrss.request.GetArticles; +import me.wizos.loread.bean.ttrss.request.GetCategories; +import me.wizos.loread.bean.ttrss.request.GetFeeds; +import me.wizos.loread.bean.ttrss.request.GetHeadlines; +import me.wizos.loread.bean.ttrss.request.GetSavedItemIds; +import me.wizos.loread.bean.ttrss.request.GetUnreadItemIds; +import me.wizos.loread.bean.ttrss.request.LoginParam; +import me.wizos.loread.bean.ttrss.request.SubscribeToFeed; +import me.wizos.loread.bean.ttrss.request.UnsubscribeFeed; +import me.wizos.loread.bean.ttrss.request.UpdateArticle; +import me.wizos.loread.bean.ttrss.result.ArticleItem; +import me.wizos.loread.bean.ttrss.result.CategoryItem; +import me.wizos.loread.bean.ttrss.result.FeedItem; +import me.wizos.loread.bean.ttrss.result.SubscribeToFeedResult; +import me.wizos.loread.bean.ttrss.result.TTRSSLoginResult; +import me.wizos.loread.bean.ttrss.result.TinyResponse; +import me.wizos.loread.bean.ttrss.result.UpdateArticleResult; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Header; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +/** + * Created by Wizos on 2019/11/23. + */ + +public interface LoreadService { + // Post请求的文本参数则用注解@Field来声明,同时还必须给方法添加注解@FormUrlEncoded来告知Retrofit参数为表单参数,如果只为参数增加@Field注解,而不给方法添加@FormUrlEncoded注解运行时会抛异常。 + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call> login( + @NonNull @Body LoginParam loginParam + ); + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call>> getCategories( + @Header("authorization") String authorization, + @NonNull @Body GetCategories getCategories + ); + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call>> getFeeds( + @Header("authorization") String authorization, + @NonNull @Body GetFeeds getFeeds + ); + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call> getUnreadItemIds( + @Header("authorization") String authorization, + @NonNull @Body GetUnreadItemIds getUnreadItemIds + ); + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call> getSavedItemIds( + @Header("authorization") String authorization, + @NonNull @Body GetSavedItemIds getSavedItemIds + ); + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call>> getHeadlines( + @Header("authorization") String authorization, + @NonNull @Body GetHeadlines getHeadlines + ); + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call>> getArticles( + @Header("authorization") String authorization, + @NonNull @Body GetArticles getArticles + ); + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call> updateArticle( + @Header("authorization") String authorization, + @NonNull @Body UpdateArticle updateArticle + ); + + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call> subscribeToFeed( + @Header("authorization") String authorization, + @NonNull @Body SubscribeToFeed subscribeToFeed + ); + + @Headers("Accept: application/json") + @POST("plugins.local/loread/") + Call> unsubscribeFeed( + @Header("authorization") String authorization, + @NonNull @Body UnsubscribeFeed unsubscribeFeed + ); + +// @Headers("Accept: application/json") +// @POST("subscriptions") +// Call> editFeed( +// @NonNull @Body EditFeed editFeed +// ); +} diff --git a/app/src/main/java/me/wizos/loread/network/api/OAuthApi.java b/app/src/main/java/me/wizos/loread/network/api/OAuthApi.java new file mode 100644 index 0000000..64f3f42 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/OAuthApi.java @@ -0,0 +1,29 @@ +package me.wizos.loread.network.api; + +import java.io.IOException; + +import me.wizos.loread.network.callback.CallbackX; + +public abstract class OAuthApi extends AuthApi { + + abstract public String getOAuthUrl(); + + /** + * 获取access_token,refresh_token,expires_in + * + * @param authorizationCode + * @return + * @throws IOException + */ + //abstract public String getAccessToken(String authorizationCode) throws IOException; + abstract public void getAccessToken(String authorizationCode, CallbackX cb); + /** + * 刷新access_token,refresh_token,expires_in + * + * @return + * @throws IOException + */ + abstract public String refreshingAccessToken(String refreshToken) throws IOException; + abstract public void refreshingAccessToken(String refreshToken, CallbackX cb); + +} diff --git a/app/src/main/java/me/wizos/loread/network/api/TinyRSSApi.java b/app/src/main/java/me/wizos/loread/network/api/TinyRSSApi.java new file mode 100644 index 0000000..26fbaad --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/TinyRSSApi.java @@ -0,0 +1,543 @@ +package me.wizos.loread.network.api; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArrayMap; + +import com.google.gson.GsonBuilder; +import com.hjq.toast.ToastUtils; +import com.jeremyliao.liveeventbus.LiveEventBus; +import com.lzy.okgo.callback.StringCallback; +import com.lzy.okgo.exception.HttpException; +import com.socks.library.KLog; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.activity.login.LoginResult; +import me.wizos.loread.bean.feedly.input.EditFeed; +import me.wizos.loread.bean.ttrss.request.GetArticles; +import me.wizos.loread.bean.ttrss.request.GetCategories; +import me.wizos.loread.bean.ttrss.request.GetFeeds; +import me.wizos.loread.bean.ttrss.request.GetHeadlines; +import me.wizos.loread.bean.ttrss.request.GetSavedItemIds; +import me.wizos.loread.bean.ttrss.request.GetUnreadItemIds; +import me.wizos.loread.bean.ttrss.request.LoginParam; +import me.wizos.loread.bean.ttrss.request.SubscribeToFeed; +import me.wizos.loread.bean.ttrss.request.UnsubscribeFeed; +import me.wizos.loread.bean.ttrss.request.UpdateArticle; +import me.wizos.loread.bean.ttrss.result.ArticleItem; +import me.wizos.loread.bean.ttrss.result.CategoryItem; +import me.wizos.loread.bean.ttrss.result.FeedItem; +import me.wizos.loread.bean.ttrss.result.SubscribeToFeedResult; +import me.wizos.loread.bean.ttrss.result.TTRSSLoginResult; +import me.wizos.loread.bean.ttrss.result.TinyResponse; +import me.wizos.loread.bean.ttrss.result.UpdateArticleResult; +import me.wizos.loread.config.ArticleActionConfig; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.Category; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.db.FeedCategory; +import me.wizos.loread.network.HttpClientManager; +import me.wizos.loread.network.SyncWorker; +import me.wizos.loread.network.callback.CallbackX; +import me.wizos.loread.utils.StringUtils; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +import static me.wizos.loread.utils.StringUtils.getString; + +/** + * Created by Wizos on 2019/2/8. + */ + +public class TinyRSSApi extends AuthApi implements LoginInterface{ + private TinyRSSService service; + private static String OFFICIAL_BASE_URL = "https://example.com"; + + public TinyRSSApi() { + this(App.i().getUser().getHost()); + } + + public TinyRSSApi(String host) { + if (!TextUtils.isEmpty(host)) { + TinyRSSApi.OFFICIAL_BASE_URL = host; + }else { + ToastUtils.show(R.string.empty_host); + } + + if (!TinyRSSApi.OFFICIAL_BASE_URL.endsWith("/")) { + TinyRSSApi.OFFICIAL_BASE_URL = TinyRSSApi.OFFICIAL_BASE_URL + "/"; + } + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(TinyRSSApi.OFFICIAL_BASE_URL) // 设置网络请求的Url地址, 必须以/结尾 + .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 设置数据解析器 + .client(HttpClientManager.i().ttrssHttpClient()) + .build(); + service = retrofit.create(TinyRSSService.class); + } + + public static void setHost(String host) { + TinyRSSApi.OFFICIAL_BASE_URL = host; + KLog.i("HOST 地址:" + OFFICIAL_BASE_URL ); + } + + public LoginResult login(String accountId, String accountPd) throws IOException { + LoginParam loginParam = new LoginParam(); + loginParam.setUser(accountId); + loginParam.setPassword(accountPd); + TinyResponse loginResultTTRSSResponse = service.login(loginParam).execute().body(); + LoginResult loginResult = new LoginResult(); + if (loginResultTTRSSResponse.isSuccessful()) { + return loginResult.setSuccess(true).setData(loginResultTTRSSResponse.getContent().getSession_id()); + } else { + return loginResult.setSuccess(false).setData(loginResultTTRSSResponse.getContent().getSession_id()); + } + } + + public void login(String accountId, String accountPd,CallbackX cb){ + LoginParam loginParam = new LoginParam(); + loginParam.setUser(accountId); + loginParam.setPassword(accountPd); + service.login(loginParam).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, Response> response) { + if(response.isSuccessful()){ + TinyResponse loginResultTTRSSResponse = response.body(); + if( loginResultTTRSSResponse.isSuccessful()){ + cb.onSuccess(loginResultTTRSSResponse.getContent().getSession_id()); + return; + } + cb.onFailure(App.i().getString(R.string.login_failed_reason, loginResultTTRSSResponse.toString())); + }else { + cb.onFailure(App.i().getString(R.string.login_failed_reason, response.message())); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure(App.i().getString(R.string.login_failed_reason, t.getMessage())); + } + }); + } + + public void fetchUserInfo(CallbackX cb){ + cb.onFailure(App.i().getString(R.string.temporarily_not_supported)); + } + //private long syncTimeMillis; + + @Override + public void sync() { + try { + long startSyncTimeMillis = System.currentTimeMillis(); + String uid = App.i().getUser().getId(); + + KLog.e("3 - 同步订阅源信息:获取分类"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.sync_feed_info)); + + // 获取分类 + TinyResponse> categoryItemsTTRSSResponse = service.getCategories( new GetCategories(getAuthorization()) ).execute().body(); + KLog.e("获取回应:" + categoryItemsTTRSSResponse); + if (!categoryItemsTTRSSResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + + Iterator categoryItemsIterator = categoryItemsTTRSSResponse.getContent().iterator(); + CategoryItem TTRSSCategoryItem; + ArrayList categories = new ArrayList<>(); + while (categoryItemsIterator.hasNext()) { + TTRSSCategoryItem = categoryItemsIterator.next(); + if (Integer.parseInt(TTRSSCategoryItem.getId()) < 1) { + continue; + } + categories.add(TTRSSCategoryItem.convert()); + } + + // 获取feed + TinyResponse> feedItemsTTRSSResponse = service.getFeeds(new GetFeeds(getAuthorization())).execute().body(); + if (!feedItemsTTRSSResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + + Iterator feedItemsIterator = feedItemsTTRSSResponse.getContent().iterator(); + FeedItem ttrssFeedItem; + ArrayList feeds = new ArrayList<>(); + ArrayList feedCategories = new ArrayList<>(feedItemsTTRSSResponse.getContent().size()); + FeedCategory feedCategoryTmp; + while (feedItemsIterator.hasNext()) { + ttrssFeedItem = feedItemsIterator.next(); + Feed feed = ttrssFeedItem.convert2Feed(); + feed.setUid(uid); + feeds.add(feed); + feedCategoryTmp = new FeedCategory(uid, String.valueOf(ttrssFeedItem.getId()), String.valueOf(ttrssFeedItem.getCatId())); + feedCategories.add(feedCategoryTmp); + } + + // 如果在获取到数据的时候就保存,那么到这里同步断了的话,可能系统内的文章就找不到响应的分组,所有放到这里保存。 + // 覆盖保存,只会保留最新一份。(比如在云端将文章移到的新的分组) + coverSaveFeeds(feeds); + coverSaveCategories(categories); + coverFeedCategory(feedCategories); + + // 获取所有未读的资源 + KLog.e("2 - 同步文章信息"); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.sync_article_refs)); + + GetHeadlines getHeadlines = new GetHeadlines(); + getHeadlines.setSid(getAuthorization()); + Article article = CoreDB.i().articleDao().getLastArticle(uid); + if (null != article) { + getHeadlines.setSince_id(article.getId()); + } + + + TinyResponse idsResponse; + // 获取未读资源 + idsResponse = service.getUnreadItemIds( new GetUnreadItemIds(getAuthorization()) ).execute().body(); + assert idsResponse != null; + if (!idsResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + KLog.e("未读" + idsResponse.getContent()); + HashSet unreadRefsSet = handleUnreadRefs( idsResponse.getContent().split(",") ); + + // 获取加星资源 + idsResponse = service.getSavedItemIds(new GetSavedItemIds(getAuthorization())).execute().body(); + assert idsResponse != null; + if (!idsResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + HashSet staredRefsSet = handleStaredRefs( idsResponse.getContent().split(",") ); + + + HashSet idRefsSet = new HashSet<>(); + idRefsSet.addAll(unreadRefsSet); + idRefsSet.addAll(staredRefsSet); + + KLog.i("文章id资源:" + idRefsSet ); + ArrayList ids = new ArrayList<>(idRefsSet); + + int hadFetchCount, needFetchCount, num; + //ArrayMap> classArticlesMap = new ArrayMap>(); + + needFetchCount = ids.size(); + hadFetchCount = 0; + + GetArticles getArticles = new GetArticles(getAuthorization()); + KLog.e("1 - 同步文章内容" + needFetchCount + " " ); + + TinyResponse> ttrssArticleItemsResponse; + ArrayList
articles; + + while (needFetchCount > 0) { + num = Math.min(needFetchCount, fetchContentCntForEach); + getArticles.setArticleIds( ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num) ); + ttrssArticleItemsResponse = service.getArticles(getArticles).execute().body(); + if (!ttrssArticleItemsResponse.isSuccessful()) { + throw new HttpException("获取失败"); + } + List items = ttrssArticleItemsResponse.getContent(); + articles = new ArrayList<>(items.size()); + long syncTimeMillis = System.currentTimeMillis(); + for (ArticleItem item : items) { + articles.add(item.convert(new ArticleChanger() { + @Override + public Article change(Article article) { + article.setCrawlDate(syncTimeMillis); + article.setUid(uid); + return article; + } + })); + } + + CoreDB.i().articleDao().insert(articles); + needFetchCount = ids.size() - hadFetchCount; + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( App.i().getString(R.string.sync_article_content, hadFetchCount, ids.size()) ); + } + + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.clear_article)); + deleteExpiredArticles(); + handleDuplicateArticle(); + handleCrawlDate(); + updateCollectionCount(); + + // 获取文章全文 + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post(getString(R.string.fetch_article_full_content)); + fetchReadability(uid, startSyncTimeMillis); + + // 为所有新增的加星文章自动生成tag + handleNotTagStarArticles(uid, startSyncTimeMillis); + // 执行文章自动处理脚本 + ArticleActionConfig.i().exeRules(uid,startSyncTimeMillis); + // 清理无文章的tag + //clearNotArticleTags(uid); + + // 提示更新完成 + LiveEventBus.get(SyncWorker.NEW_ARTICLE_NUMBER).post(ids.size()); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + }catch (IllegalStateException e){ + handleException(e, "同步失败:IllegalState异常 " + e.getMessage()); + }catch (HttpException e) { + handleException(e, "同步失败:Http异常 " + e.message()); + } catch (ConnectException e) { + handleException(e, "同步失败:Connect异常"); + } catch (SocketTimeoutException e) { + handleException(e, "同步失败:Socket超时"); + } catch (IOException e) { + handleException(e, "同步失败:IO异常"); + } catch (RuntimeException e) { + handleException(e, "同步失败:Runtime异常"); + } + } + + private void handleException(Exception e, String msg) { + KLog.e(msg); + ToastUtils.show(msg); + LiveEventBus.get(SyncWorker.SYNC_PROCESS_FOR_SUBTITLE).post( null ); + } + + @Override + public void renameTag(String tagId, String targetName, CallbackX cb) { + cb.onFailure("暂时不支持"); + } + + public void addFeed(EditFeed editFeed, CallbackX cb) { + SubscribeToFeed subscribeToFeed = new SubscribeToFeed(getAuthorization()); + subscribeToFeed.setFeed_url(editFeed.getId()); + if (editFeed.getCategoryItems() != null && editFeed.getCategoryItems().size() != 0) { + subscribeToFeed.setCategory_id(editFeed.getCategoryItems().get(0).getId()); + } + service.subscribeToFeed(subscribeToFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, Response> response) { + if (response.isSuccessful() && response.body().isSuccessful()) { + KLog.e("添加成功" + response.body().toString()); + cb.onSuccess("添加成功"); + } else { + cb.onFailure("响应失败"); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure("添加失败"); + KLog.e("添加失败"); + } + }); + } + + @Override + public void renameFeed(String feedId, String renamedTitle, CallbackX cb) { + cb.onFailure("暂时不支持"); + } + + /** + * 订阅,编辑feed + * + * @param feedId + * @param feedTitle + * @param categoryItems + * @param cb + */ + public void editFeed(@NonNull String feedId, @Nullable String feedTitle, @Nullable ArrayList categoryItems, StringCallback cb) { + } + + + @Override + public void editFeedCategories(List lastCategoryItems, EditFeed editFeed, CallbackX cb) { + cb.onFailure("暂时不支持"); + } + + public void unsubscribeFeed(String feedId,CallbackX cb) { + UnsubscribeFeed unsubscribeFeed = new UnsubscribeFeed(getAuthorization()); + unsubscribeFeed.setFeedId(Integer.parseInt(feedId)); + service.unsubscribeFeed(unsubscribeFeed).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, Response> response) { + if(response.isSuccessful() && null != response.body() && null != response.body().getContent() && "OK".equals(response.body().getContent().get("status"))){ + if(cb!=null){ + cb.onSuccess(null); + } + }else { + cb.onFailure(response.body()); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure(t.getMessage()); + } + }); + } + + + private void markArticles(int field, int mode, List ids,CallbackX cb) { + UpdateArticle updateArticle = new UpdateArticle(getAuthorization()); + updateArticle.setArticle_ids(StringUtils.join(",", ids)); + updateArticle.setField(field); + updateArticle.setMode(mode); + service.updateArticle(updateArticle).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, Response> response) { + if (response.isSuccessful() ){ + if(cb!=null){ + cb.onSuccess(null); + } + }else { + if(cb!=null){ + cb.onFailure("修改失败,原因未知"); + } + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + if(cb!=null){ + cb.onFailure("修改失败,原因未知"); + } + } + }); + } + + private void markArticle(int field, int mode, String articleId,CallbackX cb) { + UpdateArticle updateArticle = new UpdateArticle(getAuthorization()); + updateArticle.setArticle_ids(articleId); + updateArticle.setField(field); + updateArticle.setMode(mode); + service.updateArticle(updateArticle).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(retrofit2.Call> call, Response> response) { + if (response.isSuccessful() ){ + cb.onSuccess(null); + }else { + cb.onFailure("修改失败,原因未知"); + } + } + + @Override + public void onFailure(retrofit2.Call> call, Throwable t) { + cb.onFailure("修改失败,原因未知"); + } + }); + } + + public void markArticleListReaded(List articleIds,CallbackX cb) { + markArticles(2, 0, articleIds, cb); + } + + public void markArticleReaded(String articleId, CallbackX cb) { + markArticle(2, 0, articleId, cb); + } + + public void markArticleUnread(String articleId, CallbackX cb) { + markArticle(2, 1, articleId, cb); + } + + public void markArticleStared(String articleId, CallbackX cb) { + markArticle(0, 1, articleId, cb); + } + + public void markArticleUnstar(String articleId,CallbackX cb) { + markArticle(0, 0, articleId, cb); + } + + private HashSet handleUnreadRefs(String[] ids) { + KLog.i("处理未读资源:" + ids.length ); + String uid = App.i().getUser().getId(); + List
localUnreadArticles = CoreDB.i().articleDao().getUnreadNoOrder(uid); + + Map localUnreadArticlesMap = new ArrayMap<>(localUnreadArticles.size()); + List
changedArticles = new ArrayList<>(); + // 筛选下来,最终要去云端获取内容的未读Refs的集合 + HashSet tempUnreadIds = new HashSet<>(ids.length); + // 数据量大的一方 + for (Article article : localUnreadArticles) { + localUnreadArticlesMap.put(article.getId(), article); + } + // 数据量小的一方 + Article article; + for (String articleId : ids) { + article = localUnreadArticlesMap.get(articleId); + if (article != null) { + localUnreadArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(uid,articleId); + if (article != null && article.getReadStatus() == App.STATUS_READED) { + article.setReadStatus(App.STATUS_UNREAD); + changedArticles.add(article); + } else { + // 本地无,而云端有,加入要请求的未读资源 + tempUnreadIds.add(articleId+""); + } + } + } + for (Map.Entry entry : localUnreadArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localUnreadArticlesMap.get(entry.getKey()); + // 本地未读设为已读 + article.setReadStatus(App.STATUS_READED); + changedArticles.add(article); + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempUnreadIds; + } + + private HashSet handleStaredRefs(String[] ids) { + KLog.i("处理加薪资源:" + ids.length); + String uid = App.i().getUser().getId(); + List
localStarredArticles = CoreDB.i().articleDao().getStaredNoOrder(uid); + + Map localStarredArticlesMap = new ArrayMap<>(localStarredArticles.size()); + List
changedArticles = new ArrayList<>(); + HashSet tempStarredIds = new HashSet<>(ids.length); + + // 第1步,遍历数据量大的一方A,将其比对项目放入Map中 + for (Article article : localStarredArticles) { + localStarredArticlesMap.put(article.getId(), article); + } + + // 第2步,遍历数据量小的一方B。到Map中找,是否含有b中的比对项。有则XX,无则YY + Article article; + for (String articleId : ids) { + article = localStarredArticlesMap.get(articleId); + if (article != null) { + localStarredArticlesMap.remove(articleId); + } else { + article = CoreDB.i().articleDao().getById(uid,articleId); + if (article != null) { + article.setStarStatus(App.STATUS_STARED); + changedArticles.add(article); + } else { + // 本地无,而云远端有,加入要请求的加星资源 + tempStarredIds.add(articleId); + } + } + } + + for (Map.Entry entry : localStarredArticlesMap.entrySet()) { + if (entry.getKey() != null) { + article = localStarredArticlesMap.get(entry.getKey()); + article.setStarStatus(App.STATUS_UNSTAR); + changedArticles.add(article);// 取消加星 + } + } + + CoreDB.i().articleDao().update(changedArticles); + return tempStarredIds; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/api/TinyRSSService.java b/app/src/main/java/me/wizos/loread/network/api/TinyRSSService.java new file mode 100644 index 0000000..87ce980 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/api/TinyRSSService.java @@ -0,0 +1,99 @@ +package me.wizos.loread.network.api; + +import androidx.annotation.NonNull; + +import java.util.List; +import java.util.Map; + +import me.wizos.loread.bean.ttrss.request.GetArticles; +import me.wizos.loread.bean.ttrss.request.GetCategories; +import me.wizos.loread.bean.ttrss.request.GetFeeds; +import me.wizos.loread.bean.ttrss.request.GetHeadlines; +import me.wizos.loread.bean.ttrss.request.GetSavedItemIds; +import me.wizos.loread.bean.ttrss.request.GetUnreadItemIds; +import me.wizos.loread.bean.ttrss.request.LoginParam; +import me.wizos.loread.bean.ttrss.request.SubscribeToFeed; +import me.wizos.loread.bean.ttrss.request.UnsubscribeFeed; +import me.wizos.loread.bean.ttrss.request.UpdateArticle; +import me.wizos.loread.bean.ttrss.result.ArticleItem; +import me.wizos.loread.bean.ttrss.result.CategoryItem; +import me.wizos.loread.bean.ttrss.result.FeedItem; +import me.wizos.loread.bean.ttrss.result.SubscribeToFeedResult; +import me.wizos.loread.bean.ttrss.result.TTRSSLoginResult; +import me.wizos.loread.bean.ttrss.result.TinyResponse; +import me.wizos.loread.bean.ttrss.result.UpdateArticleResult; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +/** + * Created by Wizos on 2019/11/23. + */ + +public interface TinyRSSService { + @Headers("Accept: application/json") + @POST("api/") + Call> isLoginIn( + @NonNull @Body LoginParam loginParam + ); + + // Post请求的文本参数则用注解@Field来声明,同时还必须给方法添加注解@FormUrlEncoded来告知Retrofit参数为表单参数,如果只为参数增加@Field注解,而不给方法添加@FormUrlEncoded注解运行时会抛异常。 + @Headers("Accept: application/json") + @POST("api/") + Call> login( + @NonNull @Body LoginParam loginParam + ); + + @Headers("Accept: application/json") + @POST("api/") + Call>> getCategories( + @NonNull @Body GetCategories getCategories + ); + + @Headers("Accept: application/json") + @POST("api/") + Call>> getFeeds( + @NonNull @Body GetFeeds getFeeds + ); + + @Headers("Accept: application/json") + @POST("api/") + Call> getUnreadItemIds( + @NonNull @Body GetUnreadItemIds getUnreadItemIds + ); + @Headers("Accept: application/json") + @POST("api/") + Call> getSavedItemIds( + @NonNull @Body GetSavedItemIds getSavedItemIds + ); + @Headers("Accept: application/json") + @POST("api/") + Call>> getHeadlines( + @NonNull @Body GetHeadlines getHeadlines + ); + + @Headers("Accept: application/json") + @POST("api/") + Call>> getArticles( + @NonNull @Body GetArticles getArticles + ); + + @Headers("Accept: application/json") + @POST("api/") + Call> updateArticle( + @NonNull @Body UpdateArticle updateArticle + ); + + @Headers("Accept: application/json") + @POST("api/") + Call> subscribeToFeed( + @NonNull @Body SubscribeToFeed subscribeToFeed + ); + + @Headers("Accept: application/json") + @POST("api/") + Call> unsubscribeFeed( + @NonNull @Body UnsubscribeFeed unsubscribeFeed + ); +} diff --git a/app/src/main/java/me/wizos/loread/network/callback/CallbackX.java b/app/src/main/java/me/wizos/loread/network/callback/CallbackX.java new file mode 100644 index 0000000..59d3a04 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/callback/CallbackX.java @@ -0,0 +1,10 @@ +package me.wizos.loread.network.callback; + +/** + * Created by Wizos on 2019/11/24. + */ + +public interface CallbackX { + void onSuccess(T result); + void onFailure(E error); +} diff --git a/app/src/main/java/me/wizos/loread/network/glide/OkHttpAppGlideModule.java b/app/src/main/java/me/wizos/loread/network/glide/OkHttpAppGlideModule.java new file mode 100644 index 0000000..e50e0c1 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/glide/OkHttpAppGlideModule.java @@ -0,0 +1,39 @@ +package me.wizos.loread.network.glide; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.Registry; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; +import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.AppGlideModule; + +import org.jetbrains.annotations.NotNull; + +import java.io.InputStream; + +import me.wizos.loread.network.HttpClientManager; + +/** + * Created by Wizos on 2019/4/21. + */ + +@GlideModule +public class OkHttpAppGlideModule extends AppGlideModule { + @Override + public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { + registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(HttpClientManager.i().imageHttpClient())); + } + + @Override + public void applyOptions(@NotNull Context context, GlideBuilder builder) { + //int diskCacheSizeBytes = 1024 * 1024 * 100;// 100 MB = 104857600, 1GB = 1073741824 + builder.setDiskCache(new InternalCacheDiskCacheFactory(context, 1073741824)).setLogLevel(Log.ERROR); + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/InoreaderHeaderInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/InoreaderHeaderInterceptor.java new file mode 100644 index 0000000..8a7790c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/InoreaderHeaderInterceptor.java @@ -0,0 +1,21 @@ +package me.wizos.loread.network.interceptor; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +import me.wizos.loread.network.api.InoReaderApi; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class InoreaderHeaderInterceptor implements Interceptor { + @NotNull + @Override + public Response intercept(Chain chain) throws IOException { + Request.Builder builder = chain.request().newBuilder(); + builder.addHeader("AppId", InoReaderApi.APP_ID); + builder.addHeader("AppKey", InoReaderApi.APP_KEY); + return chain.proceed(builder.build()); + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/LoggerInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/LoggerInterceptor.java new file mode 100644 index 0000000..d877196 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/LoggerInterceptor.java @@ -0,0 +1,75 @@ +package me.wizos.loread.network.interceptor; + +import android.annotation.SuppressLint; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.socks.library.KLog; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import me.wizos.loread.BuildConfig; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; + +/** + * Created by Wizos on 2019/4/3. + */ + +public class LoggerInterceptor implements Interceptor { + @NotNull + @SuppressLint("DefaultLocale") + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + // 拦截请求,获取到该次请求的request + Request request = chain.request(); + // 执行本次网络请求操作,返回response信息 + Response response = chain.proceed(request); + + if (BuildConfig.DEBUG) { +// long t1 = System.nanoTime(); +// KLog.i(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); +// long t2 = System.nanoTime(); +// KLog.i(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); +// +// +// if (!HttpHeaders.hasBody(response)) { +// return response; +// } +// + ResponseBody responseBody = response.body(); + if (responseBody == null) { + return response; + } + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.buffer(); + Charset charset; + MediaType contentType = responseBody.contentType(); + if (contentType != null) { + charset = contentType.charset(StandardCharsets.UTF_8); + }else { + charset = StandardCharsets.UTF_8; + } + String bodyString = buffer.clone().readString(charset); + if (!TextUtils.isEmpty(bodyString)) { + if (bodyString.length() > 88) { + KLog.e("body---------->" + bodyString.substring(0, 88)); + } else { + KLog.e("body---------->" + bodyString); + } + } + } + return response; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/LoreadTokenInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/LoreadTokenInterceptor.java new file mode 100644 index 0000000..1fa04a4 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/LoreadTokenInterceptor.java @@ -0,0 +1,89 @@ +package me.wizos.loread.network.interceptor; + +import android.text.TextUtils; + +import com.socks.library.KLog; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import me.wizos.loread.App; +import me.wizos.loread.activity.login.LoginResult; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.network.api.LoreadApi; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; + +/** + * 自动刷新token的拦截器 + * https://www.jianshu.com/p/62ab11ddacc8 + * + * @author Wizos + * @version 1.0 + * @date 2019/4/2 + */ + +public class LoreadTokenInterceptor implements Interceptor { + @NotNull + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Response originalResponse = chain.proceed(request); + + /*通过如下的办法曲线取到请求完成的数据 + * + * 原本想通过 originalResponse.body().string() + * 去取到请求完成的数据,但是一直报错,不知道是okhttp的bug还是操作不当 + * + * 然后去看了okhttp的源码,找到了这个曲线方法,取到请求完成的数据后,根据特定的判断条件去判断token过期 + */ + ResponseBody responseBody = originalResponse.body(); + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.getBuffer(); // .buffer(); + Charset charset; + MediaType contentType = responseBody.contentType(); + if (contentType != null) { + charset = contentType.charset(StandardCharsets.UTF_8); + }else { + charset = StandardCharsets.UTF_8; + } + String bodyString = buffer.clone().readString(charset); + if (!TextUtils.isEmpty(bodyString)) { + if (bodyString.length() > 88) { + KLog.i("body->" + bodyString.substring(0, 88)); + } else { + KLog.i("body->" + bodyString); + } + } + + /***************************************/ + + //根据和服务端的约定判断token过期 + if (bodyString.contains("\"error\":\"NOT_LOGGED_IN")) { + User user = App.i().getUser(); + + // 通过一个特定的接口获取新的token,此处要用到同步的retrofit请求 + LoginResult loginResult = ((LoreadApi)App.i().getApi()).login(user.getUserId(),user.getUserPassword()); + if( loginResult.isSuccess() ){ + KLog.e("TokenInterceptor授权过期:成功重新登录 " + loginResult.getData() ); + user.setAuth(loginResult.getData()); + CoreDB.i().userDao().update(user); + App.i().getAuthApi().setAuthorization(user.getAuth()); + Request.Builder builder = request.newBuilder().header("authorization", user.getAuth()); + return chain.proceed(builder.build()); + } + } + // 否则,只需传递原始响应 + return originalResponse; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/RedirectInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/RedirectInterceptor.java new file mode 100644 index 0000000..9b2e618 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/RedirectInterceptor.java @@ -0,0 +1,31 @@ +package me.wizos.loread.network.interceptor; + +import android.text.TextUtils; + +import java.io.IOException; + +import me.wizos.loread.config.LinkRewriteConfig; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * 域名重定向 拦截器 + * + * @author Wizos + * @version 1.0 + * @date 2019/4/2 + */ + +public class RedirectInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + String newUrl = LinkRewriteConfig.i().getRedirectUrl( request.url().toString() ); + if (!TextUtils.isEmpty(newUrl)) { + // 创建一个新请求,并相应地修改它 + request = request.newBuilder().url(newUrl).build(); + } + return chain.proceed(request); + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/RefererInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/RefererInterceptor.java new file mode 100644 index 0000000..7b46c98 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/RefererInterceptor.java @@ -0,0 +1,36 @@ +//package me.wizos.loread.network.interceptor; +// +//import androidx.annotation.NonNull; +// +//import com.socks.library.KLog; +// +//import java.io.IOException; +// +//import me.wizos.loread.config.RefererRule; +//import me.wizos.loread.utils.StringUtils; +//import okhttp3.Interceptor; +//import okhttp3.Request; +//import okhttp3.Response; +// +///** +// * Referer 拦截器 +// * 用于给指定网站增加referer +// * +// * @author Wizos +// * @version 1.0 +// * @date 2019/4/2 +// */ +// +//public class RefererInterceptor implements Interceptor { +// @Override +// @NonNull +// public Response intercept(Chain chain) throws IOException { +// Request request = chain.request(); +// String referer = RefererRule.i().guessRefererByUrl(request.url().toString()); +// if (!StringUtils.isEmpty(referer)) { +// request = request.newBuilder().header("referer", referer).build(); +// } +// KLog.i("拦截到的referer:" + request.url().toString() + " , " + referer ); +// return chain.proceed(request); +// } +//} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/RelyInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/RelyInterceptor.java new file mode 100644 index 0000000..941eafa --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/RelyInterceptor.java @@ -0,0 +1,69 @@ +package me.wizos.loread.network.interceptor; + +import android.text.TextUtils; +import android.webkit.CookieManager; + +import androidx.annotation.NonNull; + +import com.socks.library.KLog; + +import java.io.IOException; + +import me.wizos.loread.config.LinkRewriteConfig; +import me.wizos.loread.config.NetworkRefererConfig; +import me.wizos.loread.config.NetworkUserAgentConfig; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * 依赖 拦截器 + * 用于给指定网站增加 referer,cookie,ua,重定向等 + * + * @author Wizos + * @version 1.0 + * @date 2019/4/2 + */ + +public class RelyInterceptor implements Interceptor { + @Override + @NonNull + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Request.Builder builder = request.newBuilder(); + String url = request.url().toString(); + boolean hasNew = false; + String newUrl = LinkRewriteConfig.i().getRedirectUrl(url).trim(); + if (!TextUtils.isEmpty(newUrl)) { + // 创建一个新请求,并相应地修改它 + builder.url(newUrl); + url = newUrl; + hasNew = true; + } + + // 使用完整的url或者topPrivateDomain都可以获取到cookie + String cookie = CookieManager.getInstance().getCookie(url); + if (!TextUtils.isEmpty(cookie)) { + builder.header("Cookie", cookie ); + hasNew = true; + } + + String referer = NetworkRefererConfig.i().guessRefererByUrl(url); + if (!TextUtils.isEmpty(referer)) { + builder.header("Referer", referer ); + hasNew = true; + } + + String ua = NetworkUserAgentConfig.i().guessUserAgentByUrl(url); + if (!TextUtils.isEmpty(ua)) { + builder.header("User-Agent", ua ); + hasNew = true; + } + KLog.i("拦截到依赖:" + url + " , " + newUrl + " , " + referer + " = " + cookie + " = " + ua ); + + if(hasNew){ + return chain.proceed(builder.build()); + } + return chain.proceed(request); + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/TTRSSTokenInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/TTRSSTokenInterceptor.java new file mode 100644 index 0000000..a35f2ea --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/TTRSSTokenInterceptor.java @@ -0,0 +1,87 @@ +package me.wizos.loread.network.interceptor; + +import android.text.TextUtils; + +import com.socks.library.KLog; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import me.wizos.loread.App; +import me.wizos.loread.activity.login.LoginResult; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.network.api.TinyRSSApi; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; + +/** + * 自动刷新token的拦截器 + * https://www.jianshu.com/p/62ab11ddacc8 + * + * @author Wizos + * @version 1.0 + * @date 2019/4/2 + */ + +public class TTRSSTokenInterceptor implements Interceptor { + @NotNull + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Response originalResponse = chain.proceed(request); + + /*通过如下的办法曲线取到请求完成的数据 + * + * 原本想通过 originalResponse.body().string() + * 去取到请求完成的数据,但是一直报错,不知道是okhttp的bug还是操作不当 + * + * 然后去看了okhttp的源码,找到了这个曲线方法,取到请求完成的数据后,根据特定的判断条件去判断token过期 + */ + ResponseBody responseBody = originalResponse.body(); + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.getBuffer(); // .buffer(); + Charset charset; + MediaType contentType = responseBody.contentType(); + if (contentType != null) { + charset = contentType.charset(StandardCharsets.UTF_8); + }else { + charset = StandardCharsets.UTF_8; + } + String bodyString = buffer.clone().readString(charset); + if (!TextUtils.isEmpty(bodyString)) { + if (bodyString.length() > 88) { + KLog.i("body->" + bodyString.substring(0, 88)); + } else { + KLog.i("body->" + bodyString); + } + } + + //根据和服务端的约定判断token过期 + if (bodyString.contains("\"error\":\"NOT_LOGGED_IN")) { + User user = App.i().getUser(); + + // 通过一个特定的接口获取新的token,此处要用到同步的retrofit请求 + LoginResult loginResult = ((TinyRSSApi)App.i().getApi()).login(user.getUserId(),user.getUserPassword()); + if( loginResult.isSuccess() ){ + KLog.e("TokenInterceptor授权过期:成功重新登录 " + loginResult.getData() ); + user.setAuth(loginResult.getData()); + CoreDB.i().userDao().update(user); + App.i().getAuthApi().setAuthorization(user.getAuth()); + Request.Builder builder = request.newBuilder().header("authorization", user.getAuth()); + return chain.proceed(builder.build()); + } + } + // 否则,只需传递原始响应 + return originalResponse; + } +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/TokenAuthenticator.java b/app/src/main/java/me/wizos/loread/network/interceptor/TokenAuthenticator.java new file mode 100644 index 0000000..0bc545f --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/TokenAuthenticator.java @@ -0,0 +1,72 @@ +package me.wizos.loread.network.interceptor; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.socks.library.KLog; + +import java.io.IOException; + +import me.wizos.loread.App; +import okhttp3.Authenticator; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.Route; + +/** + * 处理 401 Unauthorized + * Created by Wizos on 2019/4/2. + */ + +public class TokenAuthenticator implements Authenticator { + /** + * 通过okhttp提供的Authenticator接口,只有在服务端返回HTTP的状态码为401时,才会使用Authenticator接口,如果服务端设计规范,可以尝试如下方法。 + *

+ * Feedly 在输入错误的 authorization 会报: + * 1.请提供授权码 "errorMessage": "must provide authorization token" + * 2.授权码过期 "errorMessage": "token expired: 1552176000000 (-2044594)" + *

+ * Inoreader 则是返回空 + * + * @param route + * @param response + * @return + * @throws IOException + */ + @Override + public Request authenticate(Route route, @NonNull Response response) throws IOException { + KLog.e("TokenAuthenticator授权过期"); + // 重试超过限制则放弃 + if (responseCount(response) >= 2) { + return null; + } + //取出本地的refreshToken + String refreshToken = App.i().getUser().getRefreshToken(); + KLog.e("TokenAuthenticator授权过期:刷新码 " + refreshToken); + if (TextUtils.isEmpty(refreshToken)) { + return null; + } + + // 通过一个特定的接口获取新的token,此处要用到同步的retrofit请求 + String authorization = App.i().getOAuthApi().refreshingAccessToken(refreshToken); + + //要用retrofit的同步方式 +// String newToken = call.execute().body(); + KLog.e("TokenAuthenticator授权过期:授权码 " + authorization); + + return response.request().newBuilder() + .header("authorization", authorization) + .build(); + } + + + private int responseCount(Response response) { + int result = 1; + while ((response = response.priorResponse()) != null) { + result++; + } + return result; + } + +} diff --git a/app/src/main/java/me/wizos/loread/network/interceptor/TokenInterceptor.java b/app/src/main/java/me/wizos/loread/network/interceptor/TokenInterceptor.java new file mode 100644 index 0000000..39bcde5 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/network/interceptor/TokenInterceptor.java @@ -0,0 +1,85 @@ +//package me.wizos.loreadx.network.interceptor; +// +//import android.text.TextUtils; +// +//import com.socks.library.KLog; +// +//import java.io.IOException; +//import java.nio.charset.Charset; +// +//import me.wizos.loreadx.App; +//import okhttp3.Interceptor; +//import okhttp3.MediaType; +//import okhttp3.Request; +//import okhttp3.Response; +//import okhttp3.ResponseBody; +//import okio.Buffer; +//import okio.BufferedSource; +// +///** +// * 自动刷新token的拦截器 +// * https://www.jianshu.com/p/62ab11ddacc8 +// * +// * @author Wizos +// * @version 1.0 +// * @date 2019/4/2 +// */ +// +//public class TokenInterceptor implements Interceptor { +// private static final Charset UTF8 = Charset.forName("UTF-8"); +// +// @Override +// public Response intercept(Chain chain) throws IOException { +// Request request = chain.request(); +// Response originalResponse = chain.proceed(request); +// +// /**通过如下的办法曲线取到请求完成的数据 +// * +// * 原本想通过 originalResponse.body().string() +// * 去取到请求完成的数据,但是一直报错,不知道是okhttp的bug还是操作不当 +// * +// * 然后去看了okhttp的源码,找到了这个曲线方法,取到请求完成的数据后,根据特定的判断条件去判断token过期 +// */ +// ResponseBody responseBody = originalResponse.body(); +// BufferedSource source = responseBody.source(); +// source.request(Long.MAX_VALUE); // Buffer the entire body. +// Buffer buffer = source.buffer(); +// Charset charset = UTF8; +// MediaType contentType = responseBody.contentType(); +// if (contentType != null) { +// charset = contentType.charset(UTF8); +// } +// String bodyString = buffer.clone().readString(charset); +// +// if (!TextUtils.isEmpty(bodyString)) { +// if (bodyString.length() < 22) { +// KLog.e("body---------->" + bodyString.substring(0, 21)); +// } else { +// KLog.e("body---------->" + bodyString); +// } +// } +// +// /***************************************/ +// +// //根据和服务端的约定判断token过期 +// if (bodyString.contains("token expired")) { +// String refreshToken = App.i().getUser().getRefreshToken(); +// +// // 通过一个特定的接口获取新的token,此处要用到同步的retrofit请求 +// String authorization = App.i().getOAuthApi().refreshingAccessToken(refreshToken); +// +// // 创建一个新请求,并使用新令牌相应地修改它 +// Request newRequest = request.newBuilder() +// .header("authorization", authorization) +// .build(); +// +// // 重试请求 +// originalResponse.body().close(); +// KLog.e("TokenInterceptor授权过期:刷新码 " + refreshToken + ",授权码 " + authorization); +// return chain.proceed(newRequest); +// } +// +// // 否则,只需传递原始响应 +// return originalResponse; +// } +//} diff --git a/app/src/main/java/me/wizos/loread/service/AudioService.java b/app/src/main/java/me/wizos/loread/service/AudioService.java new file mode 100644 index 0000000..49c6cab --- /dev/null +++ b/app/src/main/java/me/wizos/loread/service/AudioService.java @@ -0,0 +1,483 @@ +package me.wizos.loread.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.AudioAttributes; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.speech.tts.TextToSpeech; +import android.speech.tts.UtteranceProgressListener; + +import androidx.annotation.Nullable; + +import com.hjq.toast.ToastUtils; +import com.socks.library.KLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Locale; + +import me.wizos.loread.App; +import me.wizos.loread.config.TestConfig; +import me.wizos.loread.db.Article; +import me.wizos.loread.utils.ArticleUtil; + +import static android.media.AudioAttributes.USAGE_MEDIA; +import static android.media.AudioManager.AUDIOFOCUS_GAIN; + +/** + * implements TextToSpeech.OnInitListener , TextToSpeech.OnUtteranceCompletedListener + * http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653577446&idx=2&sn=940cfe45f8da91277d1046d90368d440&scene=4#wechat_redirect + */ + +public class AudioService extends Service { + private static String TAG = "TTSService"; + private TextToSpeech textToSpeech; + private MediaPlayer player; + private String title = ""; + private int articleNo; + private String utteranceId; + private boolean isQueue; + private boolean isSpeark = false; + + @Override + public void onCreate() { + super.onCreate(); + KLog.e(TAG, "onCreate"); + + createTextToSpeech(); + //这里只执行一次,用于准备播放器 + createMediaPlayer(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + KLog.e(TAG, "onStartCommand"); + if (intent != null) { + articleNo = intent.getIntExtra("articleNo", 0); + isQueue = intent.getBooleanExtra("isQueue",false); + } + return super.onStartCommand(intent, flags, startId); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + //当执行完了onCreate后,就会执行onBind把操作歌曲的方法返回 + KLog.e(TAG, "onBind"); + return new AudioControlBinder(); + } + + +// @SuppressLint("SdCardPath") + public void speak(){ + Article article = App.i().articlesAdapter.getItem(articleNo); +// Article article = CoreDB.i().articleDao().getById(App.i().getUser().getId(),App.i().articlesAdapter.getArticleId(articleNo)); + KLog.e("准备播放" + article.getId() + " , " + utteranceId + " , " + textToSpeech.isSpeaking()); + if ( textToSpeech.isSpeaking() && article.getId().equalsIgnoreCase(utteranceId) ){ + return; + } + utteranceId = article.getId(); + + String content = ArticleUtil.getContentForSpeak(article); + + if(TestConfig.i().isTtsFile()){ + File file = new File(App.i().getExternalCacheDir() + "/" + utteranceId + ".wav"); + textToSpeech.synthesizeToFile(content,null,file,utteranceId); + //textToSpeech.synthesizeToFile(content,null,new File("/mnt/sdcard/speak.wav"),"test"); + textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() { + //这个是开始的时候。是先发声之后才会走这里 + @Override + public void onStart(String utteranceId) { + KLog.e("textToSpeech UtteranceProgressListener", "开始: " + file.getAbsolutePath() + " " + file.exists()); + if(file.exists()){ + playMusic(file); + } + } + //这个是播报完毕的时候 每一次播报完毕都会走 + @Override + public void onDone(String utteranceId) { + KLog.e("textToSpeech UtteranceProgressListener", "播放完毕 " + file.exists()); + if(file.exists()){ +// file.delete(); + } + //playMusic(file.getAbsolutePath()); + } + //错误 + @Override + public void onError(String utteranceId) { + KLog.e("textToSpeech UtteranceProgressListener", "错误"); + } + }); + }else { + // textToSpeech.speak(content,TextToSpeech.QUEUE_ADD,null, article.getId()); + textToSpeech.speak(content,TextToSpeech.QUEUE_FLUSH,null, article.getId()); + textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() { + //这个是开始的时候。是先发声之后才会走这里 + @Override + public void onStart(String utteranceId) { + KLog.i("textToSpeech onStart"); + } + //这个是播报完毕的时候 每一次播报完毕都会走 + @Override + public void onDone(String utteranceId) { + if(isQueue){ + articleNo++; + speak(); + } + + KLog.e("播放完毕" + isQueue + articleNo ); + } + //错误 + @Override + public void onError(String utteranceId) { + KLog.i("textToSpeech onError: " + utteranceId); + } + }); + } + } + + + public void playMusic(final String playUrl) { + KLog.i(TAG,"播放音乐"); + try { + if (player == null) { + player = createMediaPlayer(); + } else { + player.reset(); + player.stop(); + } + player.setDataSource(playUrl); + //异步准备 + player.prepareAsync(); + } catch (IllegalStateException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败A"); + } catch (IOException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败B"); + } + } + public void playMusic(File file) { + KLog.i(TAG,"播放音乐"); + try { + if (player == null) { + player = createMediaPlayer(); + } else { + player.reset(); + player.stop(); + } + FileInputStream fis = new FileInputStream(file); + player.setDataSource(fis.getFD()); + //异步准备 + player.prepareAsync(); + } catch (IllegalStateException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败A"); + } catch (IOException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败B"); + } + } + +// @Override +// public void onUtteranceCompleted(String utteranceId){ +// } + + //该方法包含关于歌曲的操作 + public class AudioControlBinder extends Binder { + public void setPlayStatusListener(PlayStatusListener playStatusListener) { + AudioService.this.playStatusListener = playStatusListener; + } + + public AudioService getService() { + return AudioService.this; + } + + //播放或暂停歌曲 + public void play() { + speak(); + //player.start(); + KLog.i("服务", "播放音乐"); + } + + public void pause() { + if(textToSpeech !=null){ + textToSpeech.stop(); + textToSpeech.shutdown(); + } + KLog.i("服务", "暂停音乐"); + } + +// public boolean isPrepared() { +// return prepared; +// } +// +// public int getBufferedPercent() { +// return bufferedPercent; +// } +// + //判断是否处于播放状态 + public boolean isPlaying() { + return textToSpeech.isSpeaking(); + } +// +// //返回歌曲的长度,单位为毫秒 +// public int getDuration() { +// return player.getDuration(); +// } +// +// //返回歌曲目前的进度,单位为毫秒 +// public int getCurrentPosition() { +// return player.getCurrentPosition(); +// } +// +// //设置歌曲播放的进度,单位为毫秒 +// public void seekTo(int mesc) { +// player.seekTo(mesc); +// } +// +// public void setSpeed(float speed) { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +// if (player.isPlaying()) { +// player.setPlaybackParams(player.getPlaybackParams().setSpeed(speed)); +// } else { +// player.setPlaybackParams(player.getPlaybackParams().setSpeed(speed)); +// player.pause(); // 会自动播放,所以要暂停? +// } +// } +// } +// +// public String getSpeed() { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +// return player.getPlaybackParams().getSpeed() + ""; +// } +// return getString(R.string.music_speed); +// } + + public String getTitle() { + return title; + } + } + + + private PlayStatusListener playStatusListener; + public interface PlayStatusListener { + void onPlay(); + void onPause(); // 例如在被其他音乐播放器抢占了焦点 + void onEnd(); + void onError(String cause); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (textToSpeech != null) { + textToSpeech.stop(); + textToSpeech.shutdown(); + } + } + + private int bufferedPercent = 0; + + + public void createTextToSpeech(){ + if (textToSpeech == null){ + textToSpeech = new TextToSpeech(this, new TextToSpeech.OnInitListener() { + @Override + public void onInit(int status) { + KLog.e(TAG, "onInit " + status); + if (status == TextToSpeech.SUCCESS) { + //初始化tts引擎 + int result = textToSpeech.setLanguage(Locale.CHINA); + KLog.i("初始化" + result ); + //设置参数 + // ttsParam(); + // TextToSpeech.LANG_MISSING_DATA:表示语言的数据丢失 + // TextToSpeech.LANG_NOT_SUPPORTED:不支持 + if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { + ToastUtils.show( "语音包丢失或语音不支持"); + } + speak(); + } + } + }); + } + } + + + public MediaPlayer createMediaPlayer() { + requestAudioFocus(); + player = new MediaPlayer(); + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + //添加准备好的监听 + player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + KLog.e("准备好了,开始播放"); + //mErrorCount = 0;//清空原来的错误 + //如果准备好了,就会进行这个方法 + mediaPlayer.start(); + if (playStatusListener != null) { + playStatusListener.onPlay(); + } + } + }); + player.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(MediaPlayer arg0, int percent) { + bufferedPercent = percent; + /* 打印缓冲的百分比, 如果缓冲 */ + KLog.i("缓冲了的百分比 : " + percent + " %"); + } + }); + + player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + if (playStatusListener != null) { + playStatusListener.onEnd(); + } + } + }); + + player.setOnErrorListener(new MediaPlayer.OnErrorListener() { + /** + * + * @param mp + * @param what 发生的错误类型 + * @param extra 特定于错误的额外代码。通常依赖于实现。 + * @return 如果方法处理了错误,则为True。如果没有处理错误,则为false。返回false,或者根本没有OnErrorListener,将导致调用OnCompletionListener。 + */ + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + String whatStr = "", extraStr = ""; + boolean error = false; + switch (extra) { + case MediaPlayer.MEDIA_ERROR_IO: + extraStr = "文件流错误"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_MALFORMED: + extraStr = "格式不正确"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_UNSUPPORTED: + extraStr = " 此文件不支持"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_TIMED_OUT: + extraStr = "请求超时"; + error = true; + break; + default: + extraStr = " extra=(" + extra + ")"; + break; + } + switch (what) { + case MediaPlayer.MEDIA_ERROR_UNKNOWN: + error = true; + whatStr = "未知(waht=" + what + ")"; + break; + case MediaPlayer.MEDIA_ERROR_SERVER_DIED: + error = true; + whatStr = "服务器已关闭"; + break; + default: + whatStr = "(waht:" + what + ")"; + } + + if (playStatusListener != null && error) { + playStatusListener.onError(whatStr + ", " + extraStr ); + } + KLog.e("onError播放出现错误,waht:" + what + ",extra:" + extra + ", 原因为:" + whatStr + "=" + extraStr); + // 如果方法处理了错误,则为True。如果没有处理,则为false。返回false,或者根本没有OnErrorListener,将导致调用OnCompletionListener。 + return true; + } + }); + return player; + } + + + /** + * 好像只能处理一次。 + * 结果发现 如果另外一个播放器播放获取了焦点了,那么一直就是对方的,除非对方释放了,除非你再次强求也许才会回调 focusChangeListenre,所以 + * 测试歌曲播放的时候打开qq音乐,然后开始播放 会被qq音乐获取焦点了,然后再在本软件播放然后再用qq音乐打开 无效了,因此 看来 要反复的操作,经不起折腾了,所以视频的我还是直接检测是否在播放播放就关闭了。 + * + */ + //@RequiresApi(api = Build.VERSION_CODES.O) + private void requestAudioFocus() { + // 音频管理者,用于处理各app之间的音频冲突 + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + assert audioManager != null; + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AUDIOFOCUS_GAIN) + .setOnAudioFocusChangeListener(afChangeListener) + .setAudioAttributes(new AudioAttributes.Builder().setUsage(USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()) + .build(); + audioManager.requestAudioFocus(audioFocusRequest); + }else { + audioManager.requestAudioFocus( + // 音频焦点改变监听器 + afChangeListener, + // Use the music stream. + AudioManager.STREAM_MUSIC, + // Request permanent focus. + AUDIOFOCUS_GAIN); + } + } + +// private void abandonAudioFocus() { +// audioManager.abandonAudioFocus(null); +// } + + private boolean lastAudioFocusIsLossTransient = false; + AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() { + public void onAudioFocusChange(int focusChange) { + /* + * focusChange主要有以下四种参数: + * AUDIOFOCUS_AGIN:你已经完全获得了音频焦点 + * AUDIOFOCUS_LOSS:你会长时间的失去焦点,所以不要指望在短时间内能获得。请结束自己的相关音频工作并做好收尾工作。比如另外一个音乐播放器开始播放音乐了(前提是这个另外的音乐播放器他也实现了音频焦点的控制,baidu音乐,天天静听很遗憾的就没有实现,所以他们两个是可以跟别的播放器同时播放的) + * AUDIOFOCUS_LOSS_TRANSIENT:你会短暂的失去音频焦点,你可以暂停音乐,但不要释放资源,因为你一会就可以夺回焦点并继续使用 + * AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你的焦点会短暂失去,但是你可以与新的使用者共同使用音频焦点 + */ + KLog.e("焦点转移:" + focusChange); + switch (focusChange) { + case AUDIOFOCUS_GAIN: + // Resume playback + if (player != null && !player.isPlaying() && lastAudioFocusIsLossTransient) { + player.start(); + playStatusListener.onPlay(); + lastAudioFocusIsLossTransient = false; + } + break; + case AudioManager.AUDIOFOCUS_LOSS: + // audioManager.abandonAudioFocus(afChangeListener); + // Stop playback + if (player != null && player.isPlaying()) { + player.pause(); + playStatusListener.onPause(); + } + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Pause playback + if (player != null && player.isPlaying()) { + player.pause(); + playStatusListener.onPause(); + lastAudioFocusIsLossTransient = true; + } + break; + } + + } + }; + +} diff --git a/app/src/main/java/me/wizos/loread/service/MainService.java b/app/src/main/java/me/wizos/loread/service/MainService.java index f4730fa..9f72f4b 100644 --- a/app/src/main/java/me/wizos/loread/service/MainService.java +++ b/app/src/main/java/me/wizos/loread/service/MainService.java @@ -1,356 +1,118 @@ -package me.wizos.loread.service; - -import android.app.IntentService; -import android.content.Intent; - -import com.socks.library.KLog; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import me.wizos.loread.App; -import me.wizos.loread.R; -import me.wizos.loread.activity.LoginActivity; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.db.Article; -import me.wizos.loread.db.Feed; -import me.wizos.loread.db.Tag; -import me.wizos.loread.event.Login; -import me.wizos.loread.event.Sync; -import me.wizos.loread.net.Api; -import me.wizos.loread.net.DataApi; -import me.wizos.loread.net.InoApi; -import me.wizos.loread.utils.FileUtil; -import me.wizos.loread.utils.NetworkUtil; -import me.wizos.loread.utils.StringUtil; -import me.wizos.loread.utils.ToastUtil; - -/** - * FetcherService - * 如果后台任务只有一个的话,onHandleIntent执行完,服务就会销毁,但如果后台任务有多个的话,onHandleIntent执行完最后一个任务时,服务才销毁。 - * 最后我们要知道每次执行一个后台任务就必须启动一次IntentService,而IntentService内部则是通过消息的方式发送给HandlerThread的,然后由Handler中的Looper来处理消息,而Looper是按顺序从消息队列中取任务的,也就是说IntentService的后台任务时顺序执行的,当有多个后台任务同时存在时,这些后台任务会按外部调用的顺序排队执行。 - * - * 所有的请求都被一个单独的工作者线程处理--他们或许需要足够长的时间来处理(并且不会阻塞应用的主循环),但是同一时间只能处理一个请求。 - * @author Wizos on 2017/1/7. - */ - -public class MainService extends IntentService { -// public static final String SYNC_ALL = "me.wizos.loread.sync.all"; -// public final static String SYNC_STARRED = "me.wizos.loread.sync.starred"; -// public static final String TAG_sync_config = "me.wizos.loread.sync.config"; -// public static final String CLEAR = "me.wizos.loread.clear"; - - public MainService() { - super(Api.SYNC_ALL); - } - - @Override - public void onCreate() { - super.onCreate(); - EventBus.getDefault().register(this); - } - - @Override - public void onDestroy() { - super.onDestroy(); - EventBus.getDefault().unregister(this); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onReceiveesult(Sync sync) { - int result = sync.result; -// KLog.e("接收到的数据为:" + result); - switch (result) { - case Sync.STOP: - stopSelf(); - break; - default: - break; - } - } - @Override - protected void onHandleIntent(Intent intent) { - if (intent == null) { - return; - } - - if (App.i().isSyncing || !NetworkUtil.isNetworkAvailable()) { - return; - } - - String action = intent.getAction(); - KLog.e("获取到新的任务:" + action); - if (Api.SYNC_ALL.equals(action) || Api.SYNC_HEARTBEAT.equals(action)) { - syncAll(); - } else if (Api.CLEAR.equals(action)) { - // 最后的 300 * 1000L 是留前5分钟时间的不删除 WithPref.i().getClearBeforeDay() - long time = System.currentTimeMillis() - WithPref.i().getClearBeforeDay() * 24 * 3600 * 1000L - 300 * 1000L; - handleSavedArticles(time); - clearArticles(time); - } else if (Api.LOGIN.equals(action)) { - login(intent); - } - -// else if(Api.MARK_READED.equals(action)){ -// markReaded( intent.getIntExtra("articleNo",-1) ); -// }else if(Api.MARK_UNREAD.equals(action)){ -// markUnreading( intent.getIntExtra("articleNo",-1) ); -// }else if(Api.MARK_STARED.equals(action)){ -// markStared( intent.getIntExtra("articleNo",-1) ); -// }else if(Api.MARK_UNSTAR.equals(action)){ -// markUnstar( intent.getIntExtra("articleNo",-1) ); -// }else if(Api.MARK_SAVED.equals(action)){ -// markSaved( intent.getIntExtra("articleNo",-1) ); -// }else if(Api.MARK_UNSAVE.equals(action)){ -// markUnsave( intent.getIntExtra("articleNo",-1) ); +//package me.wizos.loreadx.service; +// +//import android.content.Context; +//import android.content.Intent; +//import android.os.AsyncTask; +// +//import androidx.annotation.NonNull; +//import androidx.core.app.JobIntentService; +// +//import com.carlt.networklibs.utils.NetworkUtils; +//import com.socks.library.KLog; +// +//import java.util.ArrayList; +//import java.util.List; +// +//import me.wizos.loreadx.App; +//import me.wizos.loreadx.db.Article; +//import me.wizos.loreadx.db.CoreDB; +//import me.wizos.loreadx.utils.EncryptUtil; +//import me.wizos.loreadx.utils.FileUtil; +// +///** +// * 如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中依次去执行,执行完自动结束。 +// * 这样来避免事务处理阻塞主线程。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent请求达到,则自动停止Service; +// * 否则执行下一个Intent请求所对应的任务。 +// * Created by Wizos on 2019/3/30. +// */ +// +//public class MainService extends JobIntentService { +// public MainService() { +// super(); +// } +// +// @Override +// public void onCreate() { +// super.onCreate(); +//// EventBus.getDefault().register(this); +// } +// +// @Override +// public void onDestroy() { +// super.onDestroy(); +//// EventBus.getDefault().unregister(this); +// } +// +// /** +// * 将工作加入此服务的快捷方法。 +// */ +// public static void enqueueWork(Context context, Intent work) { +// //KLog.e("获取到新的任务:enqueueWork" ); +// enqueueWork(context, MainService.class, 100, work); +// } +// +//// @Subscribe(threadMode = ThreadMode.MAIN) +//// public void onReceiveesult(Sync sync) { +//// int result = sync.result; +////// KLog.e("接收到的数据为:" + status); +//// switch (result) { +//// case Sync.STOP: +//// stopSelf(); +//// break; +//// default: +//// break; +//// } +//// } +// +// @Override +// protected void onHandleWork(@NonNull Intent intent) { +// KLog.e("获取到新的任务,线程:" + Thread.currentThread() ); +// if (!NetworkUtils.isAvailable()) { //App.i().isSyncing || +// return; // } // -// KLog.e("文章编号:" + intent.getIntExtra("articleNo",-1) ); - } - - - private void login(Intent intent) { - App.i().isSyncing = true; - try { - boolean loginResult = DataApi.i().clientLogin(intent.getStringExtra("accountID"), intent.getStringExtra("accountPW")); - KLog.e("登录出错:", loginResult); - if (!loginResult) { - EventBus.getDefault().post(new Login(false, "Just do it")); - } - DataApi.i().fetchUserInfo(); - EventBus.getDefault().post(new Login(true)); - syncAll(); - } catch (Exception e) { - EventBus.getDefault().post(new Login(false)); - } - App.i().isSyncing = false; - } - - - private void syncAll() { - App.i().isSyncing = true; - EventBus.getDefault().post(new Sync(Sync.START)); - try { - KLog.e("5 - 同步订阅源信息"); - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_tag))); - List tagList = DataApi.i().fetchTagList(); - List feedList = DataApi.i().fetchFeedList(); - - KLog.e("4 - 同步排序信息"); - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_tag_order))); - if (WithPref.i().isOrderTagFeed()) { - tagList = DataApi.i().fetchStreamPrefs(tagList); - } else { - tagList = DataApi.i().orderTags(tagList); - } - - // 如果在获取到数据的时候就保存,那么到这里同步断了的话,可能系统内的文章就找不到响应的分组,所有放到这里保存。(比如在云端将文章移到的新的分组) - -// List localTags = WithDB.i().getTagsWithUnreadCount(); -// localTags.removeAll(tagList); -// WithDB.i().delTags(localTags); -// WithDB.i().tagDao.updateInTx(tagList); +// AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { +// @Override +// public void run() { +// String action = intent.getAction(); +// //KLog.e("获取到新的任务:" + action); +// if (App.SYNC_ALL.equals(action) || App.SYNC_HEARTBEAT.equals(action)) { +// handleExpiredArticles(); +// App.i().getApi().sync(); +// } +// } +// }); +// } +// +// private void handleExpiredArticles() { +// // EventBus.getDefault().post(new Sync(Sync.DOING, getString(R.string.clear_article))); // -// List localFeeds = WithDB.i().getFeeds(); -// localFeeds.remove(feedList); -// WithDB.i().delFeeds(localFeeds); -// WithDB.i().feedDao.updateInTx(feedList); - - WithDB.i().coverSaveTags(tagList); - - KLog.e("3 - 同步未读信息"); - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_unread_refs))); - HashSet unreadRefsList = DataApi.i().fetchUnreadRefs2(); - - - KLog.e("2 - 同步收藏信息"); - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_stared_refs))); - HashSet staredRefsList = DataApi.i().fetchStaredRefs2(); - - -// KLog.e("1 - 同步文章内容"); - ArrayList> refsList = DataApi.i().splitRefs2(unreadRefsList, staredRefsList); - int readySyncArtsCapacity = refsList.get(0).size() + refsList.get(1).size() + refsList.get(2).size(); - List ids; - int alreadySyncedArtsNum = 0, hadFetchCount, needFetchCount, num; - ArrayList

tempArticleList; - - ids = new ArrayList<>(refsList.get(0)); - needFetchCount = ids.size(); - hadFetchCount = 0; -// KLog.e("栈的数量A:" + ids.size()); - final long syncTimeMillis = System.currentTimeMillis(); - while (needFetchCount > 0) { - num = Math.min(needFetchCount, InoApi.i().fetchContentCntForEach); -// tempArticleList = DataApi.i().fetchContentsUnreadUnstar2(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)); - tempArticleList = DataApi.i().parseItemContents(InoApi.i().syncItemContents(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)), new DataApi.ArticleChanger() { - @Override - public Article change(Article article) { - article.setReadStatus(Api.UNREAD); - article.setStarStatus(Api.UNSTAR); - article.setUpdated(syncTimeMillis); - return article; - } - }); - WithDB.i().saveArticles(tempArticleList); - alreadySyncedArtsNum = alreadySyncedArtsNum + num; - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_content, alreadySyncedArtsNum, readySyncArtsCapacity))); - needFetchCount = ids.size() - hadFetchCount; - } - - - ids = new ArrayList<>(refsList.get(1)); - needFetchCount = ids.size(); - hadFetchCount = 0; -// KLog.e("栈的数量B:" + ids.size()); - while (needFetchCount > 0) { - num = Math.min(needFetchCount, InoApi.i().fetchContentCntForEach); -// tempArticleList = DataApi.i().fetchContentsReadStarred2(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)); - tempArticleList = DataApi.i().parseItemContents(InoApi.i().syncItemContents(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)), new DataApi.ArticleChanger() { - @Override - public Article change(Article article) { - article.setReadStatus(Api.READED); - article.setStarStatus(Api.STARED); - article.setUpdated(syncTimeMillis); - return article; - } - }); - - WithDB.i().saveArticles(tempArticleList); - alreadySyncedArtsNum = alreadySyncedArtsNum + num; - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_content, alreadySyncedArtsNum, readySyncArtsCapacity))); - needFetchCount = ids.size() - hadFetchCount; - } - - - ids = new ArrayList<>(refsList.get(2)); - needFetchCount = ids.size(); - hadFetchCount = 0; -// KLog.e("栈的数量C:" + ids.size()); - while (needFetchCount > 0) { - num = Math.min(needFetchCount, InoApi.i().fetchContentCntForEach); -// tempArticleList = DataApi.i().fetchContentsUnreadStarred2(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)); - tempArticleList = DataApi.i().parseItemContents(InoApi.i().syncItemContents(ids.subList(hadFetchCount, hadFetchCount = hadFetchCount + num)), new DataApi.ArticleChanger() { - @Override - public Article change(Article article) { - article.setReadStatus(Api.UNREAD); - article.setStarStatus(Api.STARED); - article.setUpdated(syncTimeMillis); - return article; - } - }); - WithDB.i().saveArticles(tempArticleList); - alreadySyncedArtsNum = alreadySyncedArtsNum + num; - EventBus.getDefault().post(new Sync(Sync.DOING, App.i().getString(R.string.main_toolbar_hint_sync_article_content, alreadySyncedArtsNum, readySyncArtsCapacity))); - needFetchCount = ids.size() - hadFetchCount; - } - - - // 如果没有同步过所有加薪文章就同步 - if (!WithPref.i().isHadSyncAllStarred()) { - ToastUtil.showLong("同步耗时较长,请耐心等待"); - DataApi.i().fetchAllStaredStreamContent(new DataApi.ArticleChanger() { - @Override - public Article change(Article article) { - article.setReadStatus(Api.READED); - article.setStarStatus(Api.STARED); - article.setUpdated(syncTimeMillis); - return article; - } - }); - WithPref.i().setHadSyncAllStarred(true); - } - WithDB.i().coverSaveFeeds(feedList); - updateFeedUnreadCount(); - - - List
articles = WithDB.i().getDuplicateArticle(); - List
articleList; - ArrayList articleIds = new ArrayList<>(); - for (Article item : articles) { - articleList = WithDB.i().getDuplicateArticle(item.getTitle(), item.getCanonical()); - if (articleList == null || articleList.size() == 0) { - continue; - } - articleList.remove(0); - for (Article article : articleList) { - article.setReadStatus(Api.READED); - articleIds.add(article.getId()); - } - } - if (articleIds.size() > 0) { - DataApi.i().markArticleListReaded(articleIds, null); - } - - EventBus.getDefault().post(new Sync(Sync.END)); - } catch (IOException e) { - e.printStackTrace(); - if (e.getMessage().equals("401")) { - needAuth(); - } - - EventBus.getDefault().post(new Sync(Sync.ERROR)); - } - App.i().isSyncing = false; - } - - private void updateFeedUnreadCount() { - ArrayList feedList = WithDB.i().getFeedsWithUnreadCount(); - WithDB.i().coverSaveFeeds(feedList); - } - - - private void needAuth() { - ToastUtil.showShort(getString(R.string.toast_login_for_auth)); - Intent loginIntent = new Intent(MainService.this, LoginActivity.class); - startActivity(loginIntent); - } - - /** - * 移动“保存且已读”的文章至一个新的文件夹 - * test 这里可能有问题,因为在我手机中有出现,DB中文章已经不存在,但文件与文件夹还存在与Box文件夹内的情况。 - */ - private void handleSavedArticles(long time) { - List
boxReadArts = WithDB.i().getArtInReadedBox(time); - KLog.i("移动文章" + boxReadArts.size()); - - for (Article article : boxReadArts) { - FileUtil.saveArticle(App.boxRelativePath, article); - article.setSaveDir(Api.SAVE_DIR_CACHE); - } - WithDB.i().saveArticles(boxReadArts); - - List
storeReadArts = WithDB.i().getArtInReadedStore(time); - KLog.i("移动文章" + storeReadArts.size()); - for (Article article : storeReadArts) { - FileUtil.saveArticle(App.storeRelativePath, article); - article.setSaveDir(Api.SAVE_DIR_CACHE); - } - WithDB.i().saveArticles(storeReadArts); - } - - - public void clearArticles(long clearTime) { - List
allArtsBeforeTime = WithDB.i().getArtInReadedUnstarLtTime(clearTime); - KLog.i("清除A:" + clearTime + "--" + allArtsBeforeTime.size()); - if (allArtsBeforeTime.size() == 0) { - return; - } - ArrayList idListMD5 = new ArrayList<>(allArtsBeforeTime.size()); - for (Article article : allArtsBeforeTime) { - idListMD5.add(StringUtil.str2MD5(article.getId())); - } - KLog.i("清除B:" + clearTime + "--" + allArtsBeforeTime.size()); - FileUtil.deleteHtmlDirList(idListMD5); - WithDB.i().delArt(allArtsBeforeTime); - System.gc(); - } - -} +// // 最后的 300 * 1000L 是留前5分钟时间的不删除 WithPref.i().getClearBeforeDay() +// long time = System.currentTimeMillis() - App.i().getUser().getCachePeriod() * 24 * 3600 * 1000L - 300 * 1000L; +// List
boxReadArts = CoreDB.i().articleDao().getReadedUnstarBeFiledLtTime(App.i().getUser().getId(), time); +// KLog.i("移动文章" + boxReadArts.size()); +// +// for (Article article : boxReadArts) { +// article.setSaveStatus(App.STATUS_IS_FILED); +// FileUtil.saveArticle(App.i().getUserBoxPath(), article); +// } +// CoreDB.i().articleDao().update(boxReadArts); +// +// List
storeReadArts = CoreDB.i().articleDao().getReadedStaredBeFiledLtTime(App.i().getUser().getId(), time); +// KLog.i("移动文章" + storeReadArts.size()); +// for (Article article : storeReadArts) { +// article.setSaveStatus(App.STATUS_IS_FILED); +// FileUtil.saveArticle(App.i().getUserStorePath(), article); +// } +// CoreDB.i().articleDao().update(storeReadArts); +// +// List
expiredArticles = CoreDB.i().articleDao().getReadedUnstarLtTime(App.i().getUser().getId(), time); +// ArrayList idListMD5 = new ArrayList<>(expiredArticles.size()); +// for (Article article : expiredArticles) { +// idListMD5.add(EncryptUtil.MD5(article.getId())); +// } +// KLog.i("清除A:" + time + "--" + expiredArticles.size()); +// FileUtil.deleteHtmlDirList(idListMD5); +// CoreDB.i().articleDao().delete(expiredArticles); +// } +//} diff --git a/app/src/main/java/me/wizos/loread/service/MusicService.java b/app/src/main/java/me/wizos/loread/service/MusicService.java new file mode 100644 index 0000000..5187863 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/service/MusicService.java @@ -0,0 +1,446 @@ +package me.wizos.loread.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import com.socks.library.KLog; + +import java.io.IOException; + +import me.wizos.loread.R; + +/** + * http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653577446&idx=2&sn=940cfe45f8da91277d1046d90368d440&scene=4#wechat_redirect + */ + +public class MusicService extends Service { + private static String TAG = "MusicService"; + private MediaPlayer player; + + @Override + public void onCreate() { + super.onCreate(); + audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + //这里只执行一次,用于准备播放器 + player = createMediaPlayer(); + KLog.e("服务", "准备播放音乐"); + } + + String playUrl; + String title = ""; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null && !TextUtils.isEmpty(intent.getDataString()) && !intent.getDataString().equals(playUrl)) { + playUrl = intent.getDataString(); + KLog.i("获取到链接:" + playUrl); + // 补救,获取 playUrl + if (TextUtils.isEmpty(playUrl)) { + playUrl = intent.getStringExtra(Intent.EXTRA_TEXT); + } + title = intent.getStringExtra("title"); + playMusic(playUrl); + } + return super.onStartCommand(intent, flags, startId); + } + + public void playMusic(final String playUrl) { + try { + if (player == null) { + player = createMediaPlayer(); + } else { + player.reset(); + player.stop(); + } + player.setDataSource(playUrl); + //异步准备 + player.prepareAsync(); + } catch (IllegalStateException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败A"); + } catch (IOException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败B"); + } + } + + public void playMusic2(final String playUrl) { + try { + if (player == null) { + player = new MediaPlayer(); + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + } else { + KLog.e("Player不为空"); + player.reset(); + player.stop(); + } + + requestAudioFocus(); + + player.setDataSource(playUrl); + //异步准备 + player.prepareAsync(); + + //添加准备好的监听 + player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + KLog.e("准备好了,开始播放"); + //如果准备好了,就会进行这个方法 + mediaPlayer.start(); + if (playStatusListener != null) { + playStatusListener.onPlay(); + } + } + }); + player.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(MediaPlayer arg0, int percent) { + bufferedPercent = percent; + /* 打印缓冲的百分比, 如果缓冲 */ + KLog.i("缓冲了的百分比 : " + percent + " %"); + } + }); + + player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + if (playStatusListener != null) { + playStatusListener.onEnd(); + } + } + }); + + player.setOnErrorListener(new MediaPlayer.OnErrorListener() { + /** + * + * @param mp + * @param what 发生的错误类型 + * @param extra 特定于错误的额外代码。通常依赖于实现。 + * @return 如果方法处理了错误,则为True。如果没有处理错误,则为false。返回false,或者根本没有OnErrorListener,将导致调用OnCompletionListener。 + */ + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + String whatStr = "", extraStr = ""; + boolean error = false; + switch (extra) { + case MediaPlayer.MEDIA_ERROR_IO: + extraStr = "文件流错误"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_MALFORMED: + extraStr = "格式不正确"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_UNSUPPORTED: + extraStr = " 此文件不支持"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_TIMED_OUT: + extraStr = "请求超时"; + error = true; + break; + default: + extraStr = " extra=(" + extra + ")"; + break; + } + switch (what) { + case MediaPlayer.MEDIA_ERROR_UNKNOWN: + error = true; + whatStr = "未知(waht=" + what + ")"; + break; + case MediaPlayer.MEDIA_ERROR_SERVER_DIED: + error = true; + whatStr = "服务器已关闭"; + break; + default: + whatStr = "(waht:" + what + ")"; + } + + if (playStatusListener != null && error) { + playStatusListener.onError(whatStr + ", " + extraStr ); + } + KLog.e("onError播放出现错误,waht:" + what + ",extra:" + extra + "," + whatStr + "=" + extraStr); + // 如果方法处理了错误,则为True。如果没有处理,则为false。返回false,或者根本没有OnErrorListener,将导致调用OnCompletionListener。 + return true; + } + }); + } catch (IllegalStateException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败A"); + } catch (IOException e) { + e.printStackTrace(); + KLog.e("设置播放地址失败B"); + } + } + + + private int bufferedPercent = 0; + public MediaPlayer createMediaPlayer() { + requestAudioFocus(); + player = new MediaPlayer(); + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + //添加准备好的监听 + player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + KLog.e("准备好了,开始播放"); + //mErrorCount = 0;//清空原来的错误 + //如果准备好了,就会进行这个方法 + mediaPlayer.start(); + if (playStatusListener != null) { + playStatusListener.onPlay(); + } + } + }); + player.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(MediaPlayer arg0, int percent) { + bufferedPercent = percent; + /* 打印缓冲的百分比, 如果缓冲 */ + KLog.i("缓冲了的百分比 : " + percent + " %"); + } + }); + + player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + if (playStatusListener != null) { + playStatusListener.onEnd(); + } + } + }); + + player.setOnErrorListener(new MediaPlayer.OnErrorListener() { + /** + * + * @param mp + * @param what 发生的错误类型 + * @param extra 特定于错误的额外代码。通常依赖于实现。 + * @return 如果方法处理了错误,则为True。如果没有处理错误,则为false。返回false,或者根本没有OnErrorListener,将导致调用OnCompletionListener。 + */ + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + KLog.e("播放出现错误,waht:" + what + ",extra:" + extra); + String whatStr = ""; + String extraStr = ""; + boolean error = false; + switch (extra) { + case MediaPlayer.MEDIA_ERROR_IO: + extraStr = "文件流错误"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_MALFORMED: + extraStr = "格式不正确"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_UNSUPPORTED: + extraStr = " 此文件不支持"; + error = true; + break; + case MediaPlayer.MEDIA_ERROR_TIMED_OUT: + extraStr = "请求超时"; + error = true; + break; + default: + extraStr = " extra=(" + extra + ")"; + break; + } + switch (what) { + case MediaPlayer.MEDIA_ERROR_UNKNOWN: + error = true; + whatStr = "未知(waht=" + what + ")"; + break; + case MediaPlayer.MEDIA_ERROR_SERVER_DIED: + error = true; + whatStr = "服务器已关闭"; + break; + default: + whatStr = "(waht:" + what + ")"; + } + + if (playStatusListener != null && error) { + playStatusListener.onError(whatStr + ", " + extraStr ); + } + KLog.e("onError播放出现错误,waht:" + what + ",extra:" + extra + "," + whatStr + "=" + extraStr); + //mErrorCount = 0; + // 如果方法处理了错误,则为True。如果没有处理,则为false。返回false,或者根本没有OnErrorListener,将导致调用OnCompletionListener。 + return true; + } + }); + return player; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + //当执行完了onCreate后,就会执行onBind把操作歌曲的方法返回 + return new MusicControlBinder(); + } + + //该方法包含关于歌曲的操作 + public class MusicControlBinder extends Binder { + public void setPlayStatusListener(PlayStatusListener playStatusListener) { + MusicService.this.playStatusListener = playStatusListener; + } + + public MusicService getService() { + return MusicService.this; + } + + //播放或暂停歌曲 + public void play() { + player.start(); + KLog.i("服务", "播放音乐"); + } + + public void pause() { + player.pause(); + KLog.i("服务", "暂停音乐"); + } + + + public int getBufferedPercent() { + return bufferedPercent; + } + + //判断是否处于播放状态 + public boolean isPlaying() { + return player.isPlaying(); + } + + //返回歌曲的长度,单位为毫秒 + public int getDuration() { + return player.getDuration(); + } + + //返回歌曲目前的进度,单位为毫秒 + public int getCurrentPosition() { + return player.getCurrentPosition(); + } + + //设置歌曲播放的进度,单位为毫秒 + public void seekTo(int mesc) { + player.seekTo(mesc); + } + + public void setSpeed(float speed) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (player.isPlaying()) { + player.setPlaybackParams(player.getPlaybackParams().setSpeed(speed)); + } else { + player.setPlaybackParams(player.getPlaybackParams().setSpeed(speed)); + player.pause(); // 会自动播放,所以要暂停? + } + } + } + + public String getSpeed() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return player.getPlaybackParams().getSpeed() + ""; + } + return getString(R.string.music_speed); + } + + public String getTitle() { + return title; + } + } + + private PlayStatusListener playStatusListener; + + public interface PlayStatusListener { + void onPlay(); + void onPause(); // 例如在被其他音乐播放器抢占了焦点 + void onEnd(); + void onError(String cause); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (player != null) { + player.setOnCompletionListener(null); + player.setOnPreparedListener(null); + player.setOnErrorListener(null); + player.reset(); + player.stop(); + player.release(); + player = null; + } + } + + private boolean lastAudioFocusIsLossTransient = false; + private AudioManager audioManager; + AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() { + public void onAudioFocusChange(int focusChange) { + /** + * focusChange主要有以下四种参数: + AUDIOFOCUS_AGIN:你已经完全获得了音频焦点 + AUDIOFOCUS_LOSS:你会长时间的失去焦点,所以不要指望在短时间内能获得。请结束自己的相关音频工作并做好收尾工作。比如另外一个音乐播放器开始播放音乐了(前提是这个另外的音乐播放器他也实现了音频焦点的控制,baidu音乐,天天静听很遗憾的就没有实现,所以他们两个是可以跟别的播放器同时播放的) + AUDIOFOCUS_LOSS_TRANSIENT:你会短暂的失去音频焦点,你可以暂停音乐,但不要释放资源,因为你一会就可以夺回焦点并继续使用 + AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你的焦点会短暂失去,但是你可以与新的使用者共同使用音频焦点 + */ + KLog.e("焦点转移:" + focusChange); + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + // Resume playback + if (player != null && !player.isPlaying() && lastAudioFocusIsLossTransient) { + player.start(); + playStatusListener.onPlay(); + lastAudioFocusIsLossTransient = false; + } + break; + case AudioManager.AUDIOFOCUS_LOSS: + // audioManager.abandonAudioFocus(afChangeListener); + // Stop playback + if (player != null && player.isPlaying()) { + player.pause(); + playStatusListener.onPause(); + } + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Pause playback + if (player != null && player.isPlaying()) { + player.pause(); + playStatusListener.onPause(); + lastAudioFocusIsLossTransient = true; + } + break; + } + + } + }; + + + /** + * 好像只能处理一次。 + * 结果发现 如果另外一个播放器播放获取了焦点了,那么一直就是对方的,除非对方释放了,除非你再次强求也许才会回调 focusChangeListenre,所以 + * 测试歌曲播放的时候打开qq音乐,然后开始播放 会被qq音乐获取焦点了,然后再在本软件播放然后再用qq音乐打开 无效了,因此 看来 要反复的操作,经不起折腾了,所以视频的我还是直接检测是否在播放播放就关闭了。 + * + * @return + */ + private boolean requestAudioFocus() { + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioManager.requestAudioFocus(afChangeListener, + // Use the music stream. + AudioManager.STREAM_MUSIC, + // Request permanent focus. + AudioManager.AUDIOFOCUS_GAIN); + } + + private void abandonAudioFocus() { + audioManager.abandonAudioFocus(null); + } + +} diff --git a/app/src/main/java/me/wizos/loread/service/NetworkStateReceiver.java b/app/src/main/java/me/wizos/loread/service/NetworkStateReceiver.java index 9425199..b6a2dd3 100644 --- a/app/src/main/java/me/wizos/loread/service/NetworkStateReceiver.java +++ b/app/src/main/java/me/wizos/loread/service/NetworkStateReceiver.java @@ -5,17 +5,27 @@ import android.content.Intent; import android.net.ConnectivityManager; +import com.socks.library.KLog; + import me.wizos.loread.utils.NetworkUtil; + /** + * Android 7.0 移除了三项隐式广播,因为隐式广播会在后台频繁启动已注册侦听这些广播的应用。删除这些广播可以显著提升设备性能和用户体验。 + * Android 7.0 以上不会收到 CONNECTIVITY_ACTION 广播,即使它们已有清单条目来请求接受这些事件的通知。 + * 在前台运行的应用如果使用 BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE。 + *

+ * Android 7.0 为了后台优化,推荐使用 JobScheduler 代替 BroadcastReceiver 来监听网络变化。 + * * @author Wizos on 2018/6/5. */ public class NetworkStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + KLog.e("接收到网络变化" + intent.getAction() + " . " + NetworkUtil.getNetWorkState()); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { - NetworkUtil.THE_NETWORK = NetworkUtil.getNetWorkState(); + NetworkUtil.getNetWorkState(); } } } diff --git a/app/src/main/java/me/wizos/loread/service/SubService.java b/app/src/main/java/me/wizos/loread/service/SubService.java deleted file mode 100644 index 87e6912..0000000 --- a/app/src/main/java/me/wizos/loread/service/SubService.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.wizos.loread.service; - -import android.app.IntentService; -import android.content.Intent; - -/** - * 这个是为了能“为 WebActivity 提前生成一个进程” - * - * @author Wizos on 2018/7/24. - */ - -public class SubService extends IntentService { - private static final String TAG = "SubService"; - - public SubService() { - super(TAG); - } - - @Override - public void onCreate() { - super.onCreate(); -// EventBus.getDefault().register(this); - } - - @Override - public void onDestroy() { - super.onDestroy(); -// EventBus.getDefault().unregister(this); - } - - // @Subscribe(threadMode = ThreadMode.MAIN) -// public void onReceiveesult(Sync sync) { -// } - @Override - protected void onHandleIntent(Intent intent) { - } -} diff --git a/app/src/main/java/me/wizos/loread/test/ArticleActivity_old.java b/app/src/main/java/me/wizos/loread/test/ArticleActivity_old.java deleted file mode 100644 index 5bce2cc..0000000 --- a/app/src/main/java/me/wizos/loread/test/ArticleActivity_old.java +++ /dev/null @@ -1,1520 +0,0 @@ -//package me.wizos.loread.activity; -// -//import android.annotation.SuppressLint; -//import android.content.ClipData; -//import android.content.ClipboardManager; -//import android.content.ComponentName; -//import android.content.Context; -//import android.content.Intent; -//import android.content.MutableContextWrapper; -//import android.content.pm.PackageManager; -//import android.content.pm.ResolveInfo; -//import android.content.res.Configuration; -//import android.graphics.Bitmap; -//import android.graphics.Color; -//import android.net.Uri; -//import android.os.Bundle; -//import android.os.Handler; -//import android.os.Looper; -//import android.support.annotation.NonNull; -//import android.support.v4.content.ContextCompat; -//import android.support.v4.view.PagerAdapter; -//import android.support.v4.view.ViewPager; -//import android.support.v7.widget.Toolbar; -//import android.text.TextUtils; -//import android.util.SparseBooleanArray; -//import android.view.KeyEvent; -//import android.view.MenuInflater; -//import android.view.MenuItem; -//import android.view.View; -//import android.view.ViewConfiguration; -//import android.view.ViewGroup; -//import android.webkit.JavascriptInterface; -//import android.webkit.WebChromeClient; -//import android.webkit.WebResourceResponse; -//import android.webkit.WebView; -//import android.webkit.WebViewClient; -//import android.widget.EditText; -//import android.widget.PopupMenu; -//import android.widget.ProgressBar; -//import android.widget.TextView; -// -//import com.afollestad.materialdialogs.DialogAction; -//import com.afollestad.materialdialogs.GravityEnum; -//import com.afollestad.materialdialogs.MaterialDialog; -//import com.afollestad.materialdialogs.Theme; -//import com.lzy.okgo.OkGo; -//import com.lzy.okgo.callback.FileCallback; -//import com.lzy.okgo.callback.StringCallback; -//import com.lzy.okgo.https.HttpsUtils; -//import com.lzy.okgo.model.Response; -//import com.lzy.okgo.request.base.Request; -//import com.socks.library.KLog; -// -//import org.jsoup.Jsoup; -//import org.jsoup.nodes.Document; -// -//import java.io.File; -//import java.io.IOException; -//import java.util.ArrayList; -//import java.util.concurrent.TimeUnit; -// -//import me.wizos.loread.App; -//import me.wizos.loread.BuildConfig; -//import me.wizos.loread.R; -//import me.wizos.loread.bean.config.GlobalConfig; -//import me.wizos.loread.common.ImageBridge; -//import me.wizos.loread.contentextractor.Extractor; -//import me.wizos.loread.data.WithDB; -//import me.wizos.loread.data.WithPref; -//import me.wizos.loread.db.Article; -//import me.wizos.loread.db.Feed; -//import me.wizos.loread.net.Api; -//import me.wizos.loread.net.DataApi; -//import me.wizos.loread.service.SubService; -//import me.wizos.loread.utils.DataUtil; -//import me.wizos.loread.utils.NetworkUtil; -//import me.wizos.loread.utils.SnackbarUtil; -//import me.wizos.loread.utils.StringUtil; -//import me.wizos.loread.utils.ToastUtil; -//import me.wizos.loread.view.IconFontView; -//import me.wizos.loread.view.SwipeRefreshLayoutS; -//import me.wizos.loread.view.WebViewS; -//import me.wizos.loread.view.colorful.Colorful; -//import me.wizos.loread.view.webview.AdBlock; -//import me.wizos.loread.view.webview.SlowlyProgressBar; -//import me.wizos.loread.view.webview.VideoImpl; -//import okhttp3.Call; -//import okhttp3.OkHttpClient; -// -///** -// * @author Wizos on 2017 -// */ -//@SuppressLint("SetJavaScriptEnabled") -//public class ArticleActivity extends BaseActivity implements ImageBridge { -// protected static final String TAG = "ArticleActivity"; -// -// private SwipeRefreshLayoutS swipeRefreshLayoutS; -// private SlowlyProgressBar slowlyProgressBar; -// private IconFontView starView, readView, saveView; -// private TextView articleNumView; -// private ViewPager viewPager; -// private WebViewS selectedWebView; -// -// private Article selectedArticle; -// private int articleNo, articleCount; -// private String articleId; -// private SparseBooleanArray mWebViewLoadedJs = new SparseBooleanArray(); -// -// private ViewPagerAdapter viewPagerAdapter; -// public Handler articleHandler = new Handler(); -// // private ArrayList articleIDs = new ArrayList<>(); -// -// -// /** -// * Video 视频播放类 -// */ -// @Override -// protected void onCreate(Bundle savedInstanceState) { -// super.onCreate(savedInstanceState); -// setContentView(R.layout.activity_article_old); -// Bundle bundle; -// if (savedInstanceState != null) { -// bundle = savedInstanceState; -// } else { -// bundle = getIntent().getExtras(); -// } -// // setSelection 没有滚动效果,直接跳到指定位置。smoothScrollToPosition 有滚动效果的 -// // 文章在列表中的位置编号,下标从 0 开始 -// articleNo = bundle.getInt("articleNo"); -// // 列表中所有的文章数目 -// articleCount = bundle.getInt("articleCount"); -// articleId = bundle.getString("articleID"); -//// articleIDs = bundle.getStringArrayList("articleIDs"); -// selectedArticle = WithDB.i().getArticle(articleId); -// if (selectedArticle == null) { -// ToastUtil.showLong("无法获取到文章"); -// } -// -// -//// KLog.e("开始初始化数据2" + articleNo + "==" + articleCount + "==" + articleId + " == " + articleIDs ); -// initToolbar(); -// initView(); // 初始化界面上的 View,将变量映射到布局上。 -// initViewPager(); -// startService(new Intent(this, SubService.class)); -// } -// -// @Override -// protected void onNewIntent(Intent intent) { -// super.onNewIntent(intent); -// } -// -// @Override -// public void onResume() { -// viewPagerAdapter.onResume(); -// super.onResume(); -// } -// -// @Override -// public void onPause() { -// viewPagerAdapter.onPause(); -// super.onPause(); -// } -// -// @Override -// protected void onDestroy() { -// // 如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。 -// // 这样做的好处是在 Acticity 退出的时候,可以避免内存泄露。因为 handler 内可能引用 Activity ,导致 Activity 退出后,内存泄漏 -// KLog.e("onDestroy:" + selectedWebView); -// OkGo.cancelAll(articleHttpClient); -// articleHandler.removeCallbacksAndMessages(null); -// onDestroyWebView(viewPager); -// viewPager.removeAllViews(); -// viewPager.clearOnPageChangeListeners(); -// super.onDestroy(); -// } -// -// @Override -// protected void onSaveInstanceState(Bundle outState) { -// outState.putInt("articleNo", 0); -// outState.putInt("articleCount", 1); -// outState.putString("articleId", articleId); -// KLog.e("自动保存:" + articleNo + "==" + articleCount + "==" + articleId); -// super.onSaveInstanceState(outState); -// } -// -// -// // 如果onCreate中bundle不为null的话,两者都可以进行恢复数据。没有区别,至于你说为什么要在onRestoreInstanceState方法中,那是因为我们的代码控制,一般是view都生成好了,然后往view上面填数据。 而onCreate的生命周期是在最初的一创建的时候,它可以用来初始化控件,onRestoreInstanceState是在onStar之后,onResume之前,可以用来填入状态数据。看个人习惯,在onCreate中一口气将view初始化完然后立刻填入数据也是可以的。 -// // 在一些特殊需求下,有可能某些动态控件并不是在onCreate里面初始化完,而是在更后面的生命周期,这时候你在onCreate里面进行恢复数据的话,那些控件还没有初始化完。 -// // 所以一般情况下你直接在onCreate中判空然后进行恢复状态是完全没有任何问题的。 -//// @Override -//// protected void onRestoreInstanceState(Bundle outState) { -//// super.onRestoreInstanceState(outState); -//// } -// -//// private void testFragment( ViewGroup container){ -//// TagFragment tagFragment = new TagFragment(); -//// getFragmentManager().beginTransaction().add(R.id.container_framelayout, tagFragment).commit(); -//// } -// -// -// private void onDestroyWebView(ViewPager viewPager) { -// try { -// WebViewS webViewS; -// for (int i = 0; i < viewPager.getChildCount(); i++) { -// // 使用自己包装的 webview -// webViewS = (WebViewS) viewPager.getChildAt(i); -// webViewS.destroy(); -// } -// } catch (Exception e) { -// e.printStackTrace(); -// KLog.e("报错:"); -// } -// } -// -// -// @JavascriptInterface -// @Override -// public void log(String paramString) { -// KLog.e("ImageBridge", "【log】" + paramString); -// } -// -// @JavascriptInterface -// @Override -// public void loadImage(String articleId, int index, String url, final String originalUrl) { -// // 去掉已经缓存了图片的 url -//// KLog.e("loadImage", "【log】" + articleId + " "+ index + " - " + url + " " + originalUrl); -// if (!url.startsWith("file:///android_asset/") || TextUtils.isEmpty(articleId) || !selectedArticle.getId().equals(articleId)) { -// return; -// } -// // 1.网络不可用 → 返回失败占位图 -// if (!NetworkUtil.isNetworkAvailable()) { -// selectedWebView.post(new Runnable() { -// @Override -// public void run() { -// selectedWebView.loadUrl("javascript:onImageLoadFailed('" + originalUrl + "')"); -// } -// }); -// } else if (WithPref.i().isDownImgOnlyWifi() && !NetworkUtil.isWiFiUsed()) { -// selectedWebView.post(new Runnable() { -// @Override -// public void run() { -// selectedWebView.loadUrl("javascript:onImageLoadNeedClick('" + originalUrl + "')"); -// } -// }); -// } else { -// downImage(articleId, index, originalUrl); -// } -// } -// -// @JavascriptInterface -// @Override -// public void openImage(String articleId, String imageFilePath, int index) { -// KLog.e("ImageBridge", "打开图片" + imageFilePath + index); -// // 直接打开内置图片浏览器 -// Intent intent = new Intent(ArticleActivity.this, ImageActivity.class); -// intent.addCategory(Intent.CATEGORY_DEFAULT); -// intent.setDataAndType(Uri.fromFile(new File(imageFilePath)), "image/*"); -// startActivity(intent); -// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); -// -// // 调用系统默认的图片查看应用 -//// Intent intentImage = new Intent(Intent.ACTION_VIEW); -//// intentImage.addCategory(Intent.CATEGORY_DEFAULT); -//// File file = new File(imageFilePath); -//// intentImage.setDataAndType(Uri.fromFile(file), "image/*"); -//// startActivity(intentImage); -// -// // 每次都要选择打开方式 -//// startActivity(Intent.createChooser(intentImage, "请选择一款")); -// -// // 调起系统默认的图片查看应用(带有选择为默认) -//// if(BuildConfig.DEBUG){ -//// Intent openImageIntent = new Intent(Intent.ACTION_VIEW); -//// openImageIntent.addCategory(Intent.CATEGORY_DEFAULT); -//// openImageIntent.setDataAndType(Uri.fromFile(new File(imageFilePath)), "image/*"); -//// getDefaultActivity(openImageIntent); -//// } -// } -// -// // 获取默认的打开方式 -// public void getDefaultActivity(Intent intent) { -// PackageManager pm = this.getPackageManager(); -// ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); -// // 如果本应用没有询问过是否要选择默认打开方式,并且没有默认的打开方式,打开默认方式选择狂 -// if (!WithPref.i().hadAskImageOpenMode() || info.activityInfo.packageName.equals("android")) { -// WithPref.i().setHadAskImageOpenMode(true); -// intent.setComponent(new ComponentName("android", "com.android.internal.app.ResolverActivity")); -// } -// startActivity(intent); -// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); -// KLog.i("打开方式", "默认打开方式信息 = " + info + ";pkgName = " + info.activityInfo.packageName); -// } -// -// // 打开选择默认打开方式的弹窗 -// public void startChooseDialog() { -//// Intent intent = new Intent(); -//// intent.setAction("android.intent.action.VIEW"); -//// intent.addCategory(Intent.CATEGORY_DEFAULT); -//// intent.setData(Uri.fromFile(new File(imageFilePath))); -//// intent.setComponent(new ComponentName("android","com.android.internal.app.ResolverActivity")); -//// startActivity(intent); -// } -// -// @JavascriptInterface -// @Override -// public void downImage(String articleId, final int index, final String originalUrl) { -// final String idInMD5 = StringUtil.str2MD5(articleId); -// final String filePath = App.externalFilesDir + "/cache/" + idInMD5 + "/" + idInMD5 + "_files/"; -// final String fileNameExt = index + "-" + StringUtil.getFileNameExtByUrl(originalUrl); -// -// if (new File(filePath + fileNameExt + Api.EXT_TMP).exists()) { -// return; -// } -//// KLog.e("开始下载图片:" + articleId + " - " + originalUrl + " " + System.currentTimeMillis() ); -// -// -// FileCallback fileCallback = new FileCallback(filePath, fileNameExt + Api.EXT_TMP) { -// @Override -// public void onSuccess(Response response) { -// new File(filePath + fileNameExt + Api.EXT_TMP).renameTo(new File(filePath + fileNameExt)); -//// KLog.e("下载成功:" + originalUrl + " - " + viewPager.findViewById(selectedPosition) + " - " + filePath + fileNameExt); -// if (articleNo != -1 && viewPager.findViewById(articleNo) != null) { -// ((WebViewS) viewPager.findViewById(articleNo)).loadUrl("javascript:onImageLoadSuccess('" + originalUrl + "','" + filePath + fileNameExt + "')"); -// } -// } -// -// // 该方法执行在主线程中 -// @Override -// public void onError(Response response) { -//// KLog.e("下载失败:" + originalUrl + " - " + viewPager.findViewById(selectedPosition) + " - " + filePath + fileNameExt); -// if (articleNo != -1 && viewPager.findViewById(articleNo) != null) { -// ((WebViewS) viewPager.findViewById(articleNo)).loadUrl("javascript:onImageLoadFailed('" + originalUrl + "')"); -// } -// new File(filePath + fileNameExt + Api.EXT_TMP).delete(); -// } -// }; -// Request request = OkGo.get(originalUrl) -// .tag(articleId) -// .client(articleHttpClient); -// -// String referer = GlobalConfig.i().guessRefererByUrl(originalUrl); -// KLog.e("图片链接是:" + originalUrl + ", 来源是:" + referer); -// if (!TextUtils.isEmpty(referer)) { -// request.headers(Api.Referer, referer); -// } -// -// request.execute(fileCallback); -//// KLog.e("下载:" + originalUrl + " 来源 " + selectedArticle.getCanonical() ); -// } -// -// /** -// * 尝试注入JS,执行setupImage()方法,但是可能因为渲染html比较慢,导致setupImage没有真正执行。 -// * 其实主要是在js文件加载中,直接调用tryInitJs,并且只初始化当前页面的setupImage -// * -// * @param articleId -// */ -// @JavascriptInterface -// @Override -// public void tryInitJs(String articleId) { -// KLog.e("准备执行 tryInitJs:" + viewPager.getCurrentItem() + " " + this.articleId + " " + articleId); -// // 防止刚生成且不在当前页的webview加载到js脚本时,就执行了setupimg函数 -// if (!this.articleId.equals(articleId)) { -// return; -// } -// mWebViewLoadedJs.put(viewPager.getCurrentItem(), false); -// selectedWebView.post(new Runnable() { -// @Override -// public void run() { -// selectedWebView.loadUrl("javascript:setTimeout(setupImage(),10)"); -// mWebViewLoadedJs.put(viewPager.getCurrentItem(), true); -// selectedWebView.setLoadJS(true); -// } -// }); -// -// } -// -// -// @JavascriptInterface -// @Override -// public void openLink(String link) { -// if (WithPref.i().isSysBrowserOpenLink()) { -// Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); -// startActivity(Intent.createChooser(intent, "选择打开方式")); -// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); -// } else { -// Intent intent = new Intent(ArticleActivity.this, WebActivity.class); -// intent.setData(Uri.parse(link)); -// startActivity(intent); -// overridePendingTransition(R.anim.fade_in, R.anim.fade_out); -// } -// } -// -// -// private void initView() { -// starView = findViewById(R.id.article_bottombar_star); -// readView = findViewById(R.id.article_bottombar_read); -// saveView = findViewById(R.id.article_bottombar_save); -// articleNumView = findViewById(R.id.art_toolbar_num); -// swipeRefreshLayoutS = findViewById(R.id.art_swipe_refresh); -// swipeRefreshLayoutS.setEnabled(false); -// if (BuildConfig.DEBUG) { -// saveView.setVisibility(View.VISIBLE); -// saveView.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// onSaveClick(view); -// } -// }); -// articleNumView.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// showArticleInfo(view); -// } -// }); -// } -// } -// -// private Toolbar toolbar; -// -// private void initToolbar() { -// toolbar = findViewById(R.id.art_toolbar); -// setSupportActionBar(toolbar); -// getSupportActionBar().setHomeButtonEnabled(true); -// getSupportActionBar().setDisplayHomeAsUpEnabled(true); -// getSupportActionBar().setDisplayShowTitleEnabled(false); -//// 白色箭头 -//// Drawable upArrow = getResources().getDrawable(R.drawable.mz_ic_sb_back); -//// upArrow.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP); -//// getSupportActionBar().setHomeAsUpIndicator(upArrow); // 替换返回箭头 -// slowlyProgressBar = new SlowlyProgressBar((ProgressBar) findViewById(R.id.article_progress_bar)); -// toolbar.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// if (articleHandler.hasMessages(Api.MSG_DOUBLE_TAP) && selectedWebView != null) { -// articleHandler.removeMessages(Api.MSG_DOUBLE_TAP); -// selectedWebView.scrollTo(0, 0); -// } else { -// articleHandler.sendEmptyMessageDelayed(Api.MSG_DOUBLE_TAP, ViewConfiguration.getDoubleTapTimeout()); -// } -// } -// }); -// } -// -// -// public void initViewPager() { -// viewPager = findViewById(R.id.art_viewpager); -// viewPager.clearOnPageChangeListeners(); -// KLog.e("初始话:" + articleNo + " " + articleCount + " " + articleId + " " + App.articleList); -// try { -// KLog.e("初始话:" + App.articleList.size()); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// if (App.articleList == null) { -// articleNo = 0; -// articleCount = 1; -// App.articleList = new ArrayList<>(); -// App.articleList.add(WithDB.i().getArticle(articleId)); -// KLog.e("初始话2:" + articleNo + " " + articleCount + " " + articleId + " " + App.articleList); -// } -// -// viewPagerAdapter = new ViewPagerAdapter(); -// viewPager.setAdapter(viewPagerAdapter); -// viewPager.addOnPageChangeListener(new PageChangeListener()); -// // 本句放到 ViewPagerAdapter 的构造器中是无效的。 -// viewPager.setCurrentItem(articleNo, false); -// // 当 setCurrentItem (0) 的时候,不会调用 onPageSelected 函数,导致无法触发 initSelectedPage 函数,所以这里要手动触发。 -// if (articleNo == 0) { -// this.initSelectedPage(0); -// } -// } -// -// // SimpleOnPageChangeListener 是 ViewPager 内部,用空方法实现 OnPageChangeListener 接口的一个类。 -// // 主要是为了便于使用者,不用去实现 OnPageChangeListener 的每一个方法(没必要,好几个用不到),只需要直接继承 SimpleOnPageChangeListener 实现需要的方法即可。 -// private class PageChangeListener extends ViewPager.SimpleOnPageChangeListener { -// /** -// * 参数position,代表哪个页面被选中。 -// * 当用手指滑动翻页的时候,如果翻动成功了(滑动的距离够长),手指抬起来就会立即执行这个方法,position就是当前滑动到的页面。 -// * 如果直接setCurrentItem翻页,那position就和setCurrentItem的参数一致,这种情况在onPageScrolled执行方法前就会立即执行。 -// * 泪奔,当 setCurrentItem (0) 的时候,不会调用该函数。 -// * 点击进入 viewpager 时,该函数比 InstantiateItem 函数先执行。(之后滑动时,InstantiateItem 已经创建好了 view) -// * 所以,为了能在运行完 InstantiateItem ,有了 selectedWebView 之后再去执行 initSelectedPage 在首次进入时都不执行,放到 InstantiateItem 中。 -// */ -// @Override -// public void onPageSelected(final int pagePosition) { -// // 当知道页面被选中后:1.检查webView是否已经生成好了,若已生成则初始化所有数据(含页面的icon);2.检查webView是否加载完毕,完毕则初始化懒加载 -// initSelectedPage(pagePosition); -// } -// } -// -// public void initSelectedPage(int position) { -// swipeRefreshLayoutS.setRefreshing(false); -// initSelectedArticle(position); -// initSelectedWebView(position); -//// KLog.e("initSelectedPage结束。位置:" + position + " " + selectedWebView); -// } -// -// -// public void initSelectedArticle(int position) { -// // 取消之前那篇文章的图片下载。但是如果回到之前那篇文章,怎么恢复下载呢? -// OkGo.cancelTag(articleHttpClient, articleId); -// -// selectedArticle = App.articleList.get(position); -// articleId = selectedArticle.getId(); -//// articleId = articleIDs.get(position); -//// selectedArticle = WithDB.i().getArticle(articleId); -// -// articleNo = position; -// -// initIconState(position); -// initFeedConfig(); -// } -// -// -// public void initSelectedWebView(final int position) { -//// KLog.e("获取WebView = " + viewPager.findViewById(position) ); -// if (viewPager.findViewById(position) == null) { -// KLog.i("重新获取WebView"); -// articleHandler.postDelayed(new Runnable() { -// @Override -// public void run() { -// initSelectedWebView(position); -// } -// }, 128); -// return; -// } -// selectedWebView = viewPager.findViewById(position); -// initSelectedWebViewContent(position); -// } -// -// VideoImpl video; -// -// // (webview在实例化后,可能还在渲染html,不一定能执行js) -// private void initSelectedWebViewContent(final int position) { -//// KLog.e("初始WebView = " + mWebViewLoadedJs.get(position)); -// // 对于已经加载过的webview做过滤,防止重复加载 -// if (selectedWebView.isLoadJS()) { -// return; -// } -//// if (mWebViewLoadedJs.get(position)) { -//// return; -//// } -// -//// selectedWebView.setWebViewClient(new WebViewClientX()); -//// // 初始化视频处理类 -// video = new VideoImpl(ArticleActivity.this, selectedWebView); -// selectedWebView.setWebChromeClient(new WebChromeClientX(video)); -// -// -// Feed feed = WithDB.i().getFeed(selectedArticle.getOriginStreamId()); -// -// if (feed != null) { -// toolbar.setTitle(feed.getTitle()); -// if (Api.DISPLAY_LINK.equals(GlobalConfig.i().getDisplayMode(feed.getId()))) { -// selectedWebView.loadUrl(selectedArticle.getCanonical()); -// } else if (Api.DISPLAY_READABILITY.equals(GlobalConfig.i().getDisplayMode(feed.getId()))) { -// onReadabilityClick(); -// } else { -// tryInitJs(selectedArticle.getId()); -// } -// } else { -// toolbar.setTitle(selectedArticle.getOriginTitle()); -// tryInitJs(selectedArticle.getId()); -// } -// selectedWebView.requestFocus(); -//// mWebViewLoadedJs.put(position, true); -// selectedWebView.setLoadJS(true); -//// KLog.e("触发初始化" ); -// } -// -// -// private class ViewPagerAdapter extends PagerAdapter { -// // 功能:该函数用来判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View) -// // Note: 判断出去的view是否等于进来的view 如果为true直接复用 -// @Override -// public boolean isViewFromObject(View arg0, Object arg1) { -// return arg0 == arg1; -// } -// -// // 初始化一个 WebView 有以下几个步骤: -// // 1.设置 WebSettings -// // 2.设置 WebViewClient,①重载点击链接的时间,从而控制之后的跳转;②重载页面的开始加载事件和结束加载事件,从而控制图片和点击事件的加载 -// // 3.设置 WebChromeClient,①重载页面的加载改变事件,从而注入bugly的js监控 -// // 4.设置 DownloadListener,从而实现下载文件时使用系统的下载方法 -// // 5.设置 JS通讯 -// @Override -// public Object instantiateItem(ViewGroup container, final int position) { -// final WebViewS webView; -//// if (App.i().mWebViewCaches.size() > 0) { -//// webView = App.i().mWebViewCaches.get(0); -//// App.i().mWebViewCaches.remove(0); -//// KLog.e("WebView" , "复用" + selectedWebView); -//// } else { -// webView = new WebViewS(new MutableContextWrapper(App.i())); -// KLog.e("WebView", "创建" + selectedWebView); -//// } -// -// // 原本想放在选择 webview 页面的时候去加载,但可能由于那时页面内容已经加载所以无法设置下面这个JSInterface? -// webView.addJavascriptInterface(ArticleActivity.this, "ImageBridge"); -// webView.setWebViewClient(new WebViewClientX()); -// -// if (WithPref.i().getThemeMode() == App.Theme_Day) { -// webView.getFastScrollDelegate().setThumbDrawable(ContextCompat.getDrawable(App.i(), R.drawable.scrollbar_light)); -// } else { -// webView.getFastScrollDelegate().setThumbDrawable(ContextCompat.getDrawable(App.i(), R.drawable.scrollbar_dark)); -// } -// webView.getFastScrollDelegate().setThumbDynamicHeight(false); -// webView.getFastScrollDelegate().setThumbSize(10, 32); -// -// // 不能在这里加,不然一进该页面,iframe 类的 video 标签视频就会弹出下载框 -// // webView.setDownloadListener(new DownloadListenerS(ArticleActivity.this)); -// -// // 方便在其他地方调用 viewPager.findViewById 来找到 selectedWebView -// webView.setId(position); -// container.addView(webView); -//// mWebViewLoadedJs.put(position, false); -//// time = System.currentTimeMillis(); -// -//// Article article = WithDB.i().getArticle(articleIDs.get(position)); -//// Feed feed = WithDB.i().getFeed( WithDB.i().getArticle(articleIDs.get(position)).getOriginStreamId()); -// -// // 检查该订阅源默认显示什么。【RSS,已读,保存的网页,原始网页】 -// Feed feed = WithDB.i().getFeed(App.articleList.get(position).getOriginStreamId()); -// // 填充加载中视图 -// if (feed == null || !Api.DISPLAY_LINK.equals(GlobalConfig.i().getDisplayMode(feed.getId()))) { -// webView.post(new Runnable() { -// @Override -// public void run() { -// webView.loadData(StringUtil.getPageForDisplay(App.articleList.get(position))); -//// webView.loadData(StringUtil.getPageForDisplay( WithDB.i().getArticle(articleIDs.get(position)) ) ); -// } -// }); -// } -// -// KLog.e("执行生成webview:" + webView + " "); -//// KLog.e("加载webview文章", "开始生成2:" + selectedWebView.getId() + " " + dataList.get(position).getTitle() + " 耗时:" + ( System.currentTimeMillis() - time) ); -//// selectedWebView.loadUrl("http://player.bilibili.com/player.html?aid=25052736&cid=42366510"); -//// selectedWebView.loadUrl("https://www.bilibili.com/blackboard/html5mobileplayer.html?aid=25052736&cid=42365557"); -//// selectedWebView.loadUrl("http://v.qq.com/iframe/player.html?vid=o0318tp1ddw&tiny=0&auto=0"); -//// selectedWebView.loadUrl("http://www.iqiyi.com/"); -//// selectedWebView.loadUrl("http://tv.sohu.com/20140508/n399272261.shtml"); // 搜狐自带可以全屏播放的js -//// selectedWebView.loadUrl("http://m.youku.com/video/id_XODEzMjU1MTI4.html"); -// return webView; -// } -// -// -// // Note: 销毁预加载以外的view对象, 会把需要销毁的对象的索引位置传进来就是position -// @Override -// public void destroyItem(ViewGroup container, int position, Object object) { -// if (object == null) { -// return; -// } -// container.removeView((View) object); -//// mWebViewLoadedJs.delete(position); -// ((WebViewS) object).destroy(); -//// ((WebViewS) object).clear(); -//// App.i().mWebViewCaches.add((WebViewS) object); -//// App.i().mWebViewCaches.add(new WebViewS(new MutableContextWrapper(App.i()))); -// } -// -// @Override -// public int getCount() { -// return (null == App.articleList) ? 0 : App.articleList.size(); -//// return 1; -//// return (null == dataList) ? 0 : dataList.size(); -//// return (null == articleIDs) ? 0 : articleIDs.size(); -// } -// -// /** -// * 暂停所有 webview -// */ -// public void onPause() { -//// KLog.e("停止所有webview:" + App.i().mWebViewCaches.size() ); -//// for (int i = 0; i < App.i().mWebViewCaches.size(); i++) { -//// // 先重载webview再暂停webview,这时候才真正能够停掉网页中正在播放de音视频,api 2.3.3 以上才能暂停 -//// App.i().mWebViewCaches.get(i).reload(); -//// App.i().mWebViewCaches.get(i).onPause(); -//// } -// } -// -// /** -// * 恢复当前的 webview -// */ -// public void onResume() { -//// KLog.e("恢复当前的 webview:" + App.i().mWebViewCaches.size() ); -// if (selectedWebView != null) { -// selectedWebView.onResume(); -// } -// } -// } -// -// private class WebChromeClientX extends WebChromeClient { -// VideoImpl video; -// -// WebChromeClientX(VideoImpl video) { -// this.video = video; -// } -// -// @Override -// public void onProgressChanged(WebView webView, int progress) { -// // 增加Javascript异常监控,不能增加,会造成页面卡死 -// // CrashReport.setJavascriptMonitor(webView, true); -// if (webView == ArticleActivity.this.selectedWebView && slowlyProgressBar != null) { -// slowlyProgressBar.onProgressChange(progress); -// } -// } -// -// // 表示进入全屏的时候 -// @Override -// public void onShowCustomView(View view, CustomViewCallback callback) { -// if (video != null) { -// video.onShowCustomView(view, callback); -// } -// } -// -// //表示退出全屏的时候 -// @Override -// public void onHideCustomView() { -// if (video != null) { -// video.onHideCustomView(); -// } -// } -// } -// -// -// private class WebViewClientX extends WebViewClient { -// @Deprecated -// @SuppressLint("NewApi") -// @Override -// public WebResourceResponse shouldInterceptRequest(WebView view, String url) { -// if (GlobalConfig.i().isBlockAD() && AdBlock.i().isAd(url)) { -// // 有广告的请求数据,我们直接返回空数据,注:不能直接返回null -// return new WebResourceResponse(null, null, null); -// } -// return super.shouldInterceptRequest(view, (url)); -// } -// -// /** -// * @param webView -// * @param url -// * @return 返回 true 表示你已经处理此次请求。 -// * 返回 false 表示由webview自行处理(一般都是把此url加载出来)。 -// * 返回super.shouldOverrideUrlLoading(view, url); 这个返回的方法会调用父类方法,也就是跳转至手机浏览器 -// */ -// @Override -// public boolean shouldOverrideUrlLoading(WebView webView, String url) { -// // 判断重定向的方式一 -// // 作者:胡几手,链接:https://www.jianshu.com/p/7dfb8797f893 -// WebView.HitTestResult hitTestResult = webView.getHitTestResult(); -// if (hitTestResult == null) { -// return false; -// } -// if (hitTestResult.getType() == WebView.HitTestResult.UNKNOWN_TYPE) { -// return false; -// } -// if (url.startsWith("//")) { -// url = url.replaceFirst("\\/\\/", ""); -// } -// //http和https协议开头的执行正常的流程 -// if (url.startsWith("http") || url.startsWith("https")) { -// openLink(url); -// OkGo.cancelAll(articleHttpClient); -// return true; -// } -// -// /** -// * 【scheme链接打开本地应用】(https://www.jianshu.com/p/45af72036e58) -// */ -// //其他的URL则会开启一个Acitity然后去调用原生APP -// final Intent in = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); -// if (in.resolveActivity(getPackageManager()) == null) { -// // TODO: 2018/4/25 说明系统中不存在这个activity。弹出一个Toast提示是否要用外部应用打开 -// return true; -// } -// in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); -// String name = "相应的"; -// try { -// name = "" + getPackageManager().getApplicationLabel(getPackageManager().getApplicationInfo(in.resolveActivity(getPackageManager()).getPackageName(), PackageManager.GET_META_DATA)); -// } catch (PackageManager.NameNotFoundException e) { -// e.printStackTrace(); -// } -// -// new MaterialDialog.Builder(ArticleActivity.this) -// .content("是否跳转到「" + name + "」应用?") -// .negativeText("取消") -// .positiveText(R.string.agree) -// .onPositive(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -// startActivity(in); -// overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); -// } -// }) -// .show(); -// -// -// return true; -// } -// -// @Override -// public void onPageStarted(WebView webView, String url, Bitmap favicon) { -// super.onPageStarted(webView, url, favicon); -//// KLog.e("加载开始:" + webView.getTitle() + " = " + url + " = " + webView.getId()); -//// timeMap.put(webView,System.currentTimeMillis()); -// if (slowlyProgressBar != null) { -// slowlyProgressBar.onProgressStart(); -// } -// } -// -// -// /** -// * 不能直接在这里就初始化setupImage,因为在viewpager中预加载而生成webview的时候,这里的懒加载就被触发了 -// * webView.loadUrl("javascript:setTimeout(\"setupImage()\",100)"); -// */ -// @Override -// public void onPageFinished(WebView webView, String url) { -//// KLog.e("加载完成:" + webView.getTitle() + " = " + url + " = " + webView.getId()); -// webView.getSettings().setBlockNetworkImage(false); -// super.onPageFinished(webView, url); -// } -// } -// -// -// private void initIconState(int position) { -//// if (selectedArticle.getReadState().equals(Api.ART_UNREAD)) { -// if (selectedArticle.getReadStatus() == Api.UNREAD) { -// readView.setText(getString(R.string.font_readed)); -// final String lastArticleId = selectedArticle.getId(); -// DataApi.i().markArticleReaded(articleHttpClient, selectedArticle.getId(), null); -// // 方法2 -// WithDB.i().setReaded(selectedArticle); -// -//// DataApi.i().changeUnreadCount(selectedArticle.getOriginStreamId(), -1); -//// KLog.i("【 ReadState 】" + WithDB.i().getArticle(selectedArticle.getId()).getReadState()); -//// } else if (selectedArticle.getReadState().equals(Api.ART_READED)) { -// } else if (selectedArticle.getReadStatus() == Api.READED) { -// readView.setText(getString(R.string.font_readed)); -//// } else if (selectedArticle.getReadState().equals(Api.ART_UNREADING)) { -// } else if (selectedArticle.getReadStatus() == Api.UNREADING) { -// readView.setText(getString(R.string.font_unread)); -// } -// -//// if (selectedArticle.getStarState().equals(Api.ART_UNSTAR)) { -// if (selectedArticle.getStarStatus() == Api.UNSTAR) { -// starView.setText(getString(R.string.font_unstar)); -// } else { -// starView.setText(getString(R.string.font_stared)); -// } -// if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_CACHE)) { -// saveView.setText(getString(R.string.font_unsave)); -// } else { -// saveView.setText(getString(R.string.font_saved)); -// } -// -// articleNumView.setText((position + 1) + " / " + articleCount); -//// KLog.i("=====position" + position); -// } -// -// -// public void onReadClick(View view) { -// KLog.e("loread", "被点击的是:" + selectedArticle.getTitle()); -//// if (selectedArticle.getReadState().equals(Api.ART_READED)) { -// if (selectedArticle.getReadStatus() == Api.READED) { -// readView.setText(getString(R.string.font_unread)); -// DataApi.i().markArticleUnread(selectedArticle.getId(), null); -// WithDB.i().setUnreading(selectedArticle); -// } else { -// readView.setText(getString(R.string.font_readed)); -// DataApi.i().markArticleReaded(selectedArticle.getId(), null); -// WithDB.i().setReaded(selectedArticle); -// } -// } -// -// -// public void onStarClick(View view) { -//// if (selectedArticle.getStarState().equals(Api.ART_UNSTAR)) { -// if (selectedArticle.getStarStatus() == Api.UNSTAR) { -// starView.setText(getString(R.string.font_stared)); -// DataApi.i().markArticleStared(selectedArticle.getId(), null); -//// selectedArticle.setStarState(Api.ART_STARED); -// selectedArticle.setStarStatus(Api.STARED); -// selectedArticle.setStarred(System.currentTimeMillis() / 1000); -// if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_BOX)) { -// selectedArticle.setSaveDir(Api.SAVE_DIR_STORE); -// } -// } else { -// starView.setText(getString(R.string.font_unstar)); -// DataApi.i().markArticleUnstar(selectedArticle.getId(), null); -//// selectedArticle.setStarState(Api.ART_UNSTAR); -// selectedArticle.setStarStatus(Api.UNSTAR); -// if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_STORE)) { -// selectedArticle.setSaveDir(Api.SAVE_DIR_BOX); -// } -// } -// WithDB.i().saveArticle(selectedArticle); -// } -// -// -// public void onSaveClick(View view){ -//// KLog.e("loread", "保存文件被点击"); -// if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_CACHE)) { -//// if (selectedArticle.getStarState().equals(Api.ART_STARED)) { -// if (selectedArticle.getStarStatus() == Api.STARED) { -// selectedArticle.setSaveDir(Api.SAVE_DIR_STORE); -// } else { -// selectedArticle.setSaveDir(Api.SAVE_DIR_BOX); -// } -// saveView.setText(getString(R.string.font_saved)); -// } else { -// selectedArticle.setSaveDir(Api.SAVE_DIR_CACHE); -// saveView.setText(getString(R.string.font_unsave)); -// } -// WithDB.i().saveArticle(selectedArticle); -// } -// -// -// public void clickReadability(View view) { -// onReadabilityClick(); -// } -// -// public void onReadabilityClick() { -// if (selectedWebView.isReadability()) { -// ToastUtil.showLong(getString(R.string.toast_cancel_readability)); -// selectedWebView.loadData(StringUtil.getPageForDisplay(selectedArticle)); -// selectedWebView.setReadability(false); -// return; -// } -// -// swipeRefreshLayoutS.setRefreshing(true); -// ToastUtil.showLong(getString(R.string.toast_get_readability_ing)); -// final Handler handler = new Handler(Looper.getMainLooper()); -// OkGo.get(selectedArticle.getCanonical()).client(articleHttpClient).getRawCall().enqueue(new okhttp3.Callback() { -// @Override -// public void onFailure(Call call, IOException e) { -// handler.post(new Runnable() { -// @Override -// public void run() { -// swipeRefreshLayoutS.setRefreshing(false); -// ToastUtil.showLong(getString(R.string.toast_get_readability_failure)); -// } -// }); -// } -// -// // 在Android应用中直接使用上述代码进行异步请求,并且在回调方法中操作了UI,那么你的程序就会抛出异常,并且告诉你不能在非UI线程中操作UI。 -// // 这是因为OkHttp对于异步的处理仅仅是开启了一个线程,并且在线程中处理响应。 -// // OkHttp是一个面向于Java应用而不是特定平台(Android)的框架,那么它就无法在其中使用Android独有的Handler机制。 -// @Override -// public void onResponse(Call call, okhttp3.Response response) throws IOException { -// if (response.isSuccessful()) { -// Document doc = Jsoup.parse(response.body().byteStream(), DataUtil.getCharsetFromContentType(response.body().contentType().toString()), selectedArticle.getCanonical()); -// final String content = Extractor.getContent(selectedArticle.getCanonical(), doc); -// -// handler.post(new Runnable() { -// @Override -// public void run() { -// selectedWebView.loadData(StringUtil.getPageForDisplay(selectedArticle, content)); -// KLog.e("获取到的内容:" + content ); -// ToastUtil.showLong(getString(R.string.toast_get_readability_success)); -// selectedWebView.setReadability(true); -// -// if (BuildConfig.DEBUG) { -// SnackbarUtil.Long(swipeRefreshLayoutS, "保存 Readability 内容?") -// .setAction("同意", new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// selectedArticle.setContent(content); -// WithDB.i().updateArticle(selectedArticle); -// } -// }).show(); -// } -// } -// }); -// } -// -// handler.post(new Runnable() { -// @Override -// public void run() { -// swipeRefreshLayoutS.setRefreshing(false); -// } -// }); -// } -// }); -// } -// -// private String selectedFeedDisplayMode = Api.DISPLAY_RSS; -// private EditText feedNameEdit; -// -// private void initFeedConfig(){ -// final Feed feed = WithDB.i().getFeed(selectedArticle.getOriginStreamId()); -// final View feedConfigView = findViewById(R.id.article_bottombar_feed_config); -// if (feed != null) { -// feedConfigView.setVisibility(View.VISIBLE); -// feedConfigView.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// showConfigFeedDialog(feed, feedConfigView); -// } -// }); -// } else { -// feedConfigView.setVisibility(View.GONE); -// } -// } -// -// public void showConfigFeedDialog(final Feed feed, final View feedConfigView) { -// if (feed == null) { -// return; -// } -// MaterialDialog dialog = new MaterialDialog.Builder(this) -// .title("配置该源") -// .customView(R.layout.config_feed_view2, true) -// .positiveText("确认") -// .negativeText("取消") -// .neutralText("退订") -// .neutralColor(Color.RED) -// .onNeutral(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -// DataApi.i().unsubscribeFeed(articleHttpClient, feed.getId(), new StringCallback() { -// @Override -// public void onSuccess(Response response) { -// if (!response.body().equals("OK")) { -// this.onError(response); -// return; -// } -// WithDB.i().unsubscribeFeed(feed); -// feedConfigView.setVisibility(View.GONE); -// ToastUtil.showLong("退订成功"); -// // 返回 mainActivity 页面,并且跳到下一个 tag/feed -// // KLog.e("移除" + itemView.groupPos + " " + itemView.childPos ); -// } -// -// @Override -// public void onError(Response response) { -// ToastUtil.showLong(App.i().getString(R.string.toast_unsubscribe_fail)); -// } -// }); -// -// } -// }) -// .onPositive(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -//// KLog.e("显示模式:" + selectedFeedDisplayMode); -// Feed feedx = feed; -// renameFeed(feedNameEdit.getText().toString(), feedx); -// if (!selectedFeedDisplayMode.equals(Api.DISPLAY_RSS)) { -// GlobalConfig.i().addDisplayRouter(feed.getId(), selectedFeedDisplayMode); -// } else { -// GlobalConfig.i().removeDisplayRouter(feed.getId()); -// } -// -//// if (selectedFeedGroup != null && selectedFeedGroup.getId() != null && !feed.getCategoryid().equals(selectedFeedGroup.getId())) { -//// // TODO: 2018/3/31 改变feed的分组 -//// KLog.e("改变feed的分组"); -//// } -// feedx.update(); -// GlobalConfig.i().save(); -// dialog.dismiss(); -// } -// }).build(); -// -// dialog.show(); -// -// feedNameEdit = (EditText) dialog.findViewById(R.id.feed_name_edit); -// feedNameEdit.setText(feed.getTitle()); -// -// TextView feedLink = (TextView) dialog.findViewById(R.id.feed_link); -// feedLink.setText(feed.getId().substring(5)); -// feedLink.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// //获取剪贴板管理器: -// ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); -// // 创建普通字符型ClipData -// ClipData mClipData = ClipData.newPlainText("RSS Link", feed.getId().substring(5)); -// // 将ClipData内容放到系统剪贴板里。 -// cm.setPrimaryClip(mClipData); -// ToastUtil.showShort("复制成功!"); -// } -// }); -// -// -// TextView feedOpenModeSelect = (TextView) dialog.findViewById(R.id.feed_open_mode_select); -// -// -// if (TextUtils.isEmpty(GlobalConfig.i().getDisplayMode(feed.getId()))) { -// selectedFeedDisplayMode = Api.DISPLAY_RSS; -// } else { -// selectedFeedDisplayMode = GlobalConfig.i().getDisplayMode(feed.getId()); -// } -// -// feedOpenModeSelect.setText(selectedFeedDisplayMode); -// feedOpenModeSelect.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// showDisplayModePopupMenu(view); -// } -// }); -// } -// -// public void renameFeed(final String renamedTitle, final Feed feedx) { -// KLog.e("=====" + renamedTitle + feedx.getId()); -// if (renamedTitle.equals("") || feedx.getTitle().equals(renamedTitle)) { -// return; -// } -// DataApi.i().renameFeed(articleHttpClient, feedx.getId(), renamedTitle, new StringCallback() { -// @Override -// public void onSuccess(Response response) { -// if (!response.body().equals("OK")) { -// this.onError(response); -// return; -// } -// Feed feed = feedx; -// feed.setTitle(renamedTitle); -// feed.update(); -//// WithDB.i().updateFeed(feed); -// // 由于改了 feed 的名字,而每个 article 自带的 feed 名字也得改过来。 -// WithDB.i().updateArtsFeedTitle(feed); -//// KLog.e("改了名字" + renamedTitle ); -// } -// -// @Override -// public void onError(Response response) { -// ToastUtil.showLong(App.i().getString(R.string.toast_rename_fail)); -// } -// }); -// } -// -// public void showDisplayModePopupMenu(final View view) { -// KLog.e("onClickedArticleListOrder图标被点击"); -// PopupMenu popupMenu = new PopupMenu(this, view); -// MenuInflater menuInflater = popupMenu.getMenuInflater(); -// popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { -// @Override -// public boolean onMenuItemClick(MenuItem menuItem) { -// switch (menuItem.getItemId()) { -// case R.id.display_mode_rss: -// selectedFeedDisplayMode = Api.DISPLAY_RSS; -// break; -// case R.id.display_mode_readability: -// selectedFeedDisplayMode = Api.DISPLAY_READABILITY; -// break; -// case R.id.display_mode_link: -// selectedFeedDisplayMode = Api.DISPLAY_LINK; -// break; -// default: -// selectedFeedDisplayMode = Api.DISPLAY_RSS; -// break; -// } -// KLog.e("选择:" + selectedFeedDisplayMode); -// ((TextView) view).setText(menuItem.getTitle()); -// return false; -// } -// }); -// // 加载布局文件到菜单中去 -// menuInflater.inflate(R.menu.menu_article_activity, popupMenu.getMenu()); -// popupMenu.show(); -// } -// -// -// public void showArticleInfo(View view) { -// KLog.e("文章信息"); -// if (!BuildConfig.DEBUG) { -// return; -// } -//// Article selectedArticle = WithDB.i().getArticle(articleId); -// String info = selectedArticle.getTitle() + "\n" + -// "ID=" + selectedArticle.getId() + "\n" + -// "ID-MD5=" + StringUtil.str2MD5(selectedArticle.getId()) + "\n" + -// "ReadState=" + selectedArticle.getReadState() + "\n" + -// "StarState=" + selectedArticle.getStarState() + "\n" + -// "SaveDir=" + selectedArticle.getSaveDir() + "\n" + -// "Author=" + selectedArticle.getAuthor() + "\n" + -// "Published=" + selectedArticle.getPublished() + "\n" + -// "Starred=" + selectedArticle.getStarred() + "\n" + -// "Categories=" + selectedArticle.getCategories() + "\n" + -// "CoverSrc=" + selectedArticle.getCoverSrc() + "\n" + -// "OriginHtmlUrl=" + selectedArticle.getOriginHtmlUrl() + "\n" + -// "OriginStreamId=" + selectedArticle.getOriginStreamId() + "\n" + -// "OriginTitle=" + selectedArticle.getOriginTitle() + "\n" + -// "Canonical=" + selectedArticle.getCanonical() + "\n" + -//// "Summary=" + selectedArticle.getSummary() + "\n" + -// "Content=" + selectedArticle.getContent() + "\n"; -// -// new MaterialDialog.Builder(this) -// .title(R.string.article_about_dialog_title) -// .content(info) -//// .positiveText(R.string.agree) -//// .onPositive(new MaterialDialog.SingleButtonCallback() { -//// @Override -//// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -//// //获取剪贴板管理器: -//// ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); -//// // 创建普通字符型ClipData -//// ClipData mClipData = ClipData.newPlainText("FeedId", selectedArticle.getOriginStreamId()); -//// // 将ClipData内容放到系统剪贴板里。 -//// cm.setPrimaryClip(mClipData); -//// ToastUtil.show("已复制订阅源的 RSS 地址"); -//// } -//// }) -// .positiveColorRes(R.color.material_red_400) -// .titleGravity(GravityEnum.CENTER) -// .titleColorRes(R.color.material_red_400) -// .contentColorRes(android.R.color.white) -// .backgroundColorRes(R.color.material_blue_grey_800) -// .dividerColorRes(R.color.material_teal_a400) -// .btnSelector(R.drawable.md_btn_selector_custom, DialogAction.POSITIVE) -// .positiveColor(Color.WHITE) -// .negativeColorAttr(android.R.attr.textColorSecondaryInverse) -// .theme(Theme.DARK) -// .show(); -// } -// -// /** -// * 不能使用 onBackPressed,会导致overridePendingTransition转场动画失效 -// * event.getRepeatCount() 后者为短期内重复按下的次数 -// * @return 返回真表示返回键被屏蔽掉 -// */ -// @Override -// public boolean onKeyDown(int keyCode, KeyEvent event) { -// if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { -// if (video != null && video.isPlaying()) { -// video.onHideCustomView(); -// return true; -// } -// Intent data = new Intent(); -// data.putExtra("articleNo", viewPager.getCurrentItem()); -// //注意下面的RESULT_OK常量要与回传接收的Activity中onActivityResult()方法一致 -// this.setResult(Api.ActivityResult_ArtToMain, data); -// this.finish(); -// overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); -// return true; -// } -// return super.onKeyDown(keyCode, event); -// } -// -// -// @Override -// protected Colorful.Builder buildColorful(Colorful.Builder mColorfulBuilder) { -// mColorfulBuilder -// .backgroundColor(R.id.art_root, R.attr.root_view_bg) -// // 设置 toolbar -// .backgroundColor(R.id.art_toolbar, R.attr.topbar_bg) -// .textColor(R.id.art_toolbar_num, R.attr.topbar_fg) -// // 设置中屏和底栏之间的分割线 -// .backgroundColor(R.id.article_bottombar_divider, R.attr.bottombar_divider) -// // 设置 bottombar -// .backgroundColor(R.id.article_bottombar, R.attr.bottombar_bg) -// .textColor(R.id.article_bottombar_read, R.attr.bottombar_fg) -// .textColor(R.id.article_bottombar_star, R.attr.bottombar_fg) -//// .textColor(R.id.article_bottombar_tag, R.attr.bottombar_fg) -// .textColor(R.id.article_bottombar_save, R.attr.bottombar_fg); -// return mColorfulBuilder; -// } -// -// -// private OkHttpClient articleHttpClient = new OkHttpClient.Builder() -// .readTimeout(60000L, TimeUnit.MILLISECONDS) -// .writeTimeout(60000L, TimeUnit.MILLISECONDS) -// .connectTimeout(30000L, TimeUnit.MILLISECONDS) -// .sslSocketFactory(HttpsUtils.getSslSocketFactory().sSLSocketFactory, HttpsUtils.getSslSocketFactory().trustManager) -// .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier).build(); -// -// -// @Override -// public boolean onOptionsItemSelected(MenuItem item) { -// //监听左上角的返回箭头 -// if (item.getItemId() == android.R.id.home) { -// finish(); -// overridePendingTransition(R.anim.fade_in, R.anim.out_from_bottom); -// return true; -// } -// return super.onOptionsItemSelected(item); -// } -// -// @Override -// public void onConfigurationChanged(Configuration config) { -// super.onConfigurationChanged(config); -// } -// -//// public void onTagClick(View view) { -//// final List tagsList = WithDB.i().getTags(); -//// ArrayList tags = new ArrayList<>(tagsList.size()); -//// for (Tag tag : tagsList) { -//// tags.add(tag.getTitle()); -//// } -//// new MaterialDialog.Builder(this) -//// .title(R.string.article_choose_tag_dialog_title) -//// .items(tags) -//// .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() { -//// @Override -//// public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { -//// String tagId = tagsList.get(which).getId(); -//// StringBuilder newCategories = new StringBuilder(selectedArticle.getCategories().length()); -//// String[] cateArray = selectedArticle.getCategories().replace("]", "").replace("[", "").split(","); -//// StringBuilder tempCate = new StringBuilder(); -//// for (String category : cateArray) { -//// tempCate.append(tempCate); -//// if (category.contains("user/" + App.UserID + "/label/")) { -//// if (category.substring(0, 1).equals("\"")) { -//// category = category.substring(1, category.length()); -//// } -//// if (category.substring(category.length() - 1, category.length()).equals("\"")) { -//// category = category.substring(0, category.length() - 1); -//// } -//// DataApi.i().articleRemoveTag(selectedArticle.getId(), category, null); -//// -//// KLog.i("【-】" + category); -//// } else { -//// newCategories.append(category); -//// newCategories.append(", "); -//// } -//// } -//// newCategories.append(tagId); -//// newCategories.append("]"); -//// KLog.i("【==】" + newCategories + selectedArticle.getId()); -//// selectedArticle.setCategories(newCategories.toString()); -//// DataApi.i().articleAddTag(selectedArticle.getId(), tagId, null); -//// dialog.dismiss(); -//// return true; // allow selection -//// } -//// }) -//// .show(); -//// } -// -// -//// -//// ArtHandler artHandler = new ArtHandler(this); -//// private static String content = ""; -//// private static class ArtHandler extends Handler { -//// private final WeakReference mActivity; -//// ArtHandler(ArticleActivity activity) { -//// mActivity = new WeakReference<>(activity); -//// } -//// @Override -//// public void handleMessage(final Message msg) { -//// -//// switch (msg.what) { -//// case 0: -//// new MaterialDialog.Builder(mActivity.get()) -//// .title(R.string.article_about_dialog_title) -//// .content(content) -//// .show(); -//// break; -//// default: -//// break; -//// } -//// } -//// } -// -// -//// private BottomSheetDialog dialog; -//// public void onClickMore(View view) { -//// dialog = new BottomSheetDialog(ArticleActivity.this); -//// dialog.setContentView(R.layout.article_bottom_sheet_more); -////// dialog.dismiss(); //dialog消失 -////// dialog.setCanceledOnTouchOutside(false); //触摸dialog之外的地方,dialog不消失 -//// View feedConfigView = dialog.findViewById(R.id.feed_config); -//// IconFontView feedConfig = dialog.findViewById(R.id.feed_config_icon); -//// IconFontView openLinkByBrowser = dialog.findViewById(R.id.open_link_by_browser_icon); -//// IconFontView getReadability = dialog.findViewById(R.id.get_readability_icon); -//// TextView getReadabilityTitle = dialog.findViewById(R.id.get_readability_title); -//// -//// final Feed feed = WithDB.i().getFeed(selectedArticle.getOriginStreamId()); -//// if (feed == null) { -//// feedConfigView.setVisibility(View.GONE); -//// } else if (Api.DISPLAY_READABILITY.equals(GlobalConfig.i().getDisplayMode(feed.getId()))) { -//// getReadability.setTextColor(getResources().getColor(R.color.colorPrimary)); -//// getReadabilityTitle.setTextColor(getResources().getColor(R.color.colorPrimary)); -//// getReadability.setClickable(false); -//// getReadabilityTitle.setClickable(false); -//// } -//// -//// dialog.show(); -//// -//// feedConfig.setOnClickListener(new View.OnClickListener() { -//// @Override -//// public void onClick(View view) { -//// dialog.dismiss(); -//// FeedConfigDialog feedConfigerDialog = new FeedConfigDialog(); -//// feedConfigerDialog.setOnUnsubscribeFeedListener(new StringCallback() { -//// @Override -//// public void onSuccess(Response response) { -//// if (!response.body().equals("OK")) { -//// this.onError(response); -//// return; -//// } -//// WithDB.i().unsubscribeFeed(feed); -//// -//// // 返回 mainActivity 页面,并且跳到下一个 tag/feed -//// // KLog.e("移除" + itemView.groupPos + " " + itemView.childPos ); -//// } -//// -//// @Override -//// public void onError(Response response) { -//// ToastUtil.showLong(App.i().getString(R.string.toast_unsubscribe_fail)); -//// } -//// }); -//// feedConfigerDialog.showConfigFeedDialog(ArticleActivity.this, feed); -//// } -//// }); -//// openLinkByBrowser.setOnClickListener(new View.OnClickListener() { -//// @Override -//// public void onClick(View view) { -//// dialog.dismiss(); -//// openLink(selectedArticle.getCanonical()); -//// } -//// }); -//// -//// getReadability.setOnClickListener(new View.OnClickListener() { -//// @Override -//// public void onClick(View view) { -//// dialog.dismiss(); -//// onReadabilityClick(); -//// } -//// }); -//// -////// nightTheme.setOnClickListener(new View.OnClickListener() { -////// @Override -////// public void onClick(View view) { -////// manualToggleTheme(); -////// dialog.dismiss(); -////// } -////// }); -//// } -// -//// ArrayMap timeMap = new ArrayMap<>(); -// -// /** -// * 暂时放弃以下这种跨进程方式,因为在2个进程之间数据同步比较困难。 -// * 我原本是想在主进程中操作文章的已读未读加星保存等操作,然后在次进程中用读取数据库的方式来获取最新的文章状态。 -// * 但是这是不可行的,我在主进程中修改一个状态,在次进程中获取到的还是旧的。 -// * ----------- -// * 在一个应用中包含了两个进程A和B,这两个进程同时都要操作同一个数据库, -// * 对于数据的读取进程间没有发现任何同步问题,但是在写数据时就存在一定的问题。 -// * 比如,进程A创建了一个表,此时进程B马上访问这个表就有可能出现该表不存在,无法访问的问题, -// * 为了解决这个问题,看了很多资料都没有比较好的方法,最后只能采取权宜的方法, -// * 即,对于存在问题的写数据操作放在同一个进程中完成,具体来讲就是如果进程A需要创建表,就通过广播告诉进程B,我要创建一个表, -// * 而具体表的创建工作由B来完成,而且之后对于表的写操作也由B来完成,A想要向数据库中写东西都是采取发送广播事件的方法完成。 -// * 这样做目前来看可以解决这个问题,不知道有没有人有更好的方法。 -// * ----------- -// * ContentProvider着实让人很头痛,实际上Android文档中提到,如果没有跨进程的需求,或者向其他应用分享数据的需求就不必使用ContentProvider。 -// * 但ContentProvider为数据库的管理提供了更清晰的接口,并且为了使用CursorLoader,ContentProvider是必须构建的。 -// * 此方案的使用场景:一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,同时可以避免查询数据的时候,造成UI主线程的卡顿。 -// * 缺点是此方案虽好,但是使用起来稍微麻烦,对于一般的简单数据处理有点大才小用。 -// * @param position -// */ -//// private void initIconState2(int position){ -//// Article selectedArticle = WithDB.i().getArticle(articleId); -//// if (selectedArticle.getReadState().equals(Api.ART_UNREAD)) { -//// readView.setText(getString(R.string.font_readed)); -//// Intent intent = new Intent(this, MainService.class); -//// intent.setAction(Api.MARK_READED); -//// intent.putExtra("articleNo",position); -//// startService(intent); -//// // 通知标记该文章为已读 -//// } else if (selectedArticle.getReadState().equals(Api.ART_READED)) { -//// readView.setText(getString(R.string.font_readed)); -//// } else if (selectedArticle.getReadState().equals(Api.ART_UNREADING)) { -//// readView.setText(getString(R.string.font_unread)); -//// } -//// -//// if (selectedArticle.getStarState().equals(Api.ART_UNSTAR)) { -//// starView.setText(getString(R.string.font_unstar)); -//// } else { -//// starView.setText(getString(R.string.font_stared)); -//// } -//// if (selectedArticle.getSaveDir().equals(Api.SAVE_DIR_CACHE)) { -//// saveView.setText(getString(R.string.font_unsave)); -//// } else { -//// saveView.setText(getString(R.string.font_saved)); -//// } -//// -//// articleNumView.setText((position + 1) + " / " + articleCount); -//// } -// -// -//// public void onReadClick2(View view) { -//// KLog.e("loread", "被点击的是:" + WithDB.i().getArticle(articleId).getTitle() + ",编号:" + articleNo); -//// Intent intent = new Intent(this, MainService.class); -//// intent.putExtra("articleNo",articleNo); -//// if (WithDB.i().getArticle(articleId).getReadState().equals(Api.ART_READED)) { -//// KLog.e("loread", "置为未读" ); -//// readView.setText(getString(R.string.font_unread)); -//// intent.setAction(Api.MARK_UNREAD); -//// } else { -//// KLog.e("loread", "置为已读" ); -//// readView.setText(getString(R.string.font_readed)); -//// intent.setAction(Api.MARK_READED); -//// } -//// startService(intent); -//// } -//// -//// public void onStarClick2(View view) { -//// Intent intent = new Intent(this, MainService.class); -//// intent.putExtra("articleNo",articleNo); -//// if (WithDB.i().getArticle(articleId).getStarState().equals(Api.ART_UNSTAR)) { -//// starView.setText(getString(R.string.font_stared)); -//// intent.setAction(Api.MARK_STARED); -//// } else { -//// starView.setText(getString(R.string.font_unstar)); -//// intent.setAction(Api.MARK_UNSTAR); -//// } -//// startService(intent); -//// } -//// public void onSaveClick2(View view){ -//// Intent intent = new Intent(this, MainService.class); -//// intent.putExtra("articleNo",articleNo); -//// if (WithDB.i().getArticle(articleId).getSaveDir().equals(Api.SAVE_DIR_CACHE)) { -//// saveView.setText(getString(R.string.font_saved)); -//// intent.setAction(Api.MARK_SAVED); -//// } else { -//// saveView.setText(getString(R.string.font_unsave)); -//// intent.setAction(Api.MARK_UNSAVE); -//// } -//// startService(intent); -//// } -// -// -//} diff --git a/app/src/main/java/me/wizos/loread/test/FeedConfigDialog.java b/app/src/main/java/me/wizos/loread/test/FeedConfigDialog.java deleted file mode 100644 index 07103fb..0000000 --- a/app/src/main/java/me/wizos/loread/test/FeedConfigDialog.java +++ /dev/null @@ -1,263 +0,0 @@ -//package me.wizos.loread.activity; -// -//import android.content.ClipData; -//import android.content.ClipboardManager; -//import android.content.Context; -//import android.graphics.Color; -//import android.support.annotation.NonNull; -//import android.text.TextUtils; -//import android.view.Menu; -//import android.view.MenuInflater; -//import android.view.MenuItem; -//import android.view.View; -//import android.widget.EditText; -//import android.widget.PopupMenu; -//import android.widget.TextView; -// -//import com.afollestad.materialdialogs.DialogAction; -//import com.afollestad.materialdialogs.MaterialDialog; -//import com.lzy.okgo.callback.StringCallback; -//import com.lzy.okgo.model.Response; -//import com.socks.library.KLog; -// -//import java.util.List; -// -//import me.wizos.loread.App; -//import me.wizos.loread.R; -//import me.wizos.loread.bean.config.GlobalConfig; -//import me.wizos.loread.data.WithDB; -//import me.wizos.loread.db.Feed; -//import me.wizos.loread.db.Tag; -//import me.wizos.loread.net.Api; -//import me.wizos.loread.net.DataApi; -//import me.wizos.loread.utils.ToastUtil; -// -///** -// * @author Wizos on 2018/3/31. -// */ -// -//public class FeedConfigDialog { -// private String selectedFeedDisplayMode = Api.DISPLAY_RSS; -// private Tag selectedFeedGroup; -// private EditText feedNameEdit; -// -// private StringCallback stringCallback; -// -// public void setOnUnsubscribeFeedListener(StringCallback stringCallback) { -// this.stringCallback = stringCallback; -// } -// -// public void showConfigFeedDialog(@NonNull final Context context, final Feed feed) { -// if (feed == null) { -// return; -// } -// MaterialDialog dialog = new MaterialDialog.Builder(context) -// .title("配置该源") -// .customView(R.layout.config_feed_view, true) -// .positiveText("确认") -// .negativeText("取消") -// .neutralText("退订") -// .neutralColor(Color.RED) -// .onNeutral(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -// unsubscribeFeed2(feed); -// } -// }) -// .onPositive(new MaterialDialog.SingleButtonCallback() { -// @Override -// public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { -//// KLog.e("显示模式:" + selectedFeedDisplayMode); -// -// Feed feedx = feed; -// renameFeed(feedNameEdit.getText().toString(), feedx); -// if (!selectedFeedDisplayMode.equals(Api.DISPLAY_RSS)) { -//// feed.setDisplayMode(selectedFeedDisplayMode); -// GlobalConfig.i().addDisplayRouter(feed.getId(), selectedFeedDisplayMode); -// } else { -//// feed.setDisplayMode(null); -// GlobalConfig.i().removeDisplayRouter(feed.getId()); -// } -// -// if (selectedFeedGroup != null && selectedFeedGroup.getId() != null && !feed.getCategoryid().equals(selectedFeedGroup.getId())) { -// // TODO: 2018/3/31 改变feed的分组 -// KLog.e("改变feed的分组"); -// } -// feedx.update(); -//// feedx.saveConfig(); -// GlobalConfig.i().save(); -// dialog.dismiss(); -// } -// }).build(); -// -// dialog.show(); -// -// feedNameEdit = (EditText) dialog.findViewById(R.id.feed_name_edit); -// feedNameEdit.setText(feed.getTitle()); -// -// TextView feedLink = (TextView) dialog.findViewById(R.id.feed_link); -// feedLink.setText(feed.getId().substring(5)); -// feedLink.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// //获取剪贴板管理器: -// ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); -// // 创建普通字符型ClipData -// ClipData mClipData = ClipData.newPlainText("RSS Link", feed.getId().substring(5)); -// // 将ClipData内容放到系统剪贴板里。 -// cm.setPrimaryClip(mClipData); -// ToastUtil.showShort("复制成功!"); -// } -// }); -// -// -// TextView feedOpenModeSelect = (TextView) dialog.findViewById(R.id.feed_open_mode_select); -// -//// if (TextUtils.isEmpty(feed.getDisplayMode())) { -//// selectedFeedDisplayMode = Api.DISPLAY_RSS; -//// } else { -//// selectedFeedDisplayMode = feed.getDisplayMode(); -//// } -// if (TextUtils.isEmpty(GlobalConfig.i().getDisplayMode(feed.getId()))) { -// selectedFeedDisplayMode = Api.DISPLAY_RSS; -// } else { -// selectedFeedDisplayMode = GlobalConfig.i().getDisplayMode(feed.getId()); -// } -// -// feedOpenModeSelect.setText(selectedFeedDisplayMode); -// feedOpenModeSelect.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// showDefaultDisplayModePopupMenu(context, view); -// } -// }); -// -//// TextView feedTagSelect = (TextView) dialog.findViewById(R.id.feed_tag_select); -//// feedTagSelect.setText(feed.getCategorylabel()); -//// feedTagSelect.setOnClickListener(new View.OnClickListener() { -//// @Override -//// public void onClick(View view) { -//// showClassPopupMenu( context, view ); -//// } -//// }); -// } -// -// -// public void showDefaultDisplayModePopupMenu(final Context context, final View view) { -// KLog.e("onClickedArticleListOrder图标被点击"); -// PopupMenu popupMenu = new PopupMenu(context, view); -// MenuInflater menuInflater = popupMenu.getMenuInflater(); -// popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { -// @Override -// public boolean onMenuItemClick(MenuItem menuItem) { -// switch (menuItem.getItemId()) { -// case R.id.display_mode_rss: -// selectedFeedDisplayMode = Api.DISPLAY_RSS; -// break; -//// case R.id.display_mode_readability: -//// selectedFeedDisplayMode = Api.DISPLAY_READABILITY; -//// break; -// case R.id.display_mode_link: -// selectedFeedDisplayMode = Api.DISPLAY_LINK; -// break; -// default: -// selectedFeedDisplayMode = Api.DISPLAY_RSS; -// break; -// } -// KLog.e("选择:" + selectedFeedDisplayMode); -// ((TextView) view).setText(menuItem.getTitle()); -// return false; -// } -// }); -// // 加载布局文件到菜单中去 -// menuInflater.inflate(R.menu.menu_article_activity, popupMenu.getMenu()); -// popupMenu.show(); -// } -// -// -// public void renameFeed(final String renamedTitle, final Feed feedx) { -// KLog.e("=====" + renamedTitle + feedx.getId()); -// if (renamedTitle.equals("") || feedx.getTitle().equals(renamedTitle)) { -// return; -// } -// DataApi.i().renameFeed(feedx.getId(), renamedTitle, new StringCallback() { -// @Override -// public void onSuccess(Response response) { -// if (!response.body().equals("OK")) { -// this.onError(response); -// return; -// } -// Feed feed = feedx; -// feed.setTitle(renamedTitle); -// feed.update(); -//// WithDB.i().updateFeed(feed); -// // 由于改了 feed 的名字,而每个 article 自带的 feed 名字也得改过来。 -// WithDB.i().updateArtsFeedTitle(feed); -//// KLog.e("改了名字" + renamedTitle ); -// } -// -// @Override -// public void onError(Response response) { -// ToastUtil.showLong(App.i().getString(R.string.toast_rename_fail)); -// } -// }); -// } -// -// private void unsubscribeFeed2(final Feed feed) { -// if (stringCallback == null) { -// return; -// } -// DataApi.i().unsubscribeFeed(feed.getId(), stringCallback); -// ToastUtil.showLong("退订成功"); -// } -// -// private void unsubscribeFeed(final Feed feed) { -// if (stringCallback == null) { -// return; -// } -// DataApi.i().unsubscribeFeed(feed.getId(), stringCallback); -// -// DataApi.i().unsubscribeFeed(feed.getId(), new StringCallback() { -// @Override -// public void onSuccess(Response response) { -// if (!response.body().equals("OK")) { -// this.onError(response); -// return; -// } -// WithDB.i().delFeed(feed); -// // 返回 mainActivity 页面,并且跳到下一个 tag/feed -//// KLog.e("移除" + itemView.groupPos + " " + itemView.childPos ); -//// tagListAdapter.removeChild(itemView.groupPos, itemView.childPos); -//// tagListAdapter.notifyDataSetChanged(); -// } -// -// @Override -// public void onError(Response response) { -// ToastUtil.showLong(App.i().getString(R.string.toast_unsubscribe_fail)); -// } -// }); -// } -// -// -// public void showClassPopupMenu(final Context context, final View view) { -// KLog.e("onClickedArticleListOrder图标被点击"); -// PopupMenu popupMenu = new PopupMenu(context, view); -// Menu menuList = popupMenu.getMenu(); -// final List tagsList = WithDB.i().getTags(); -// for (int i = 0, size = tagsList.size(); i < size; i++) { -// // GroupId, ItemId, Order/位置,标题 -// menuList.add(0, i, i, tagsList.get(i).getTitle()); -// } -// -// popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { -// @Override -// public boolean onMenuItemClick(MenuItem menuItem) { -// selectedFeedGroup = tagsList.get(menuItem.getItemId()); -// ((TextView) view).setText(menuItem.getTitle()); -// KLog.e("选择:" + menuItem.getTitle() + menuItem.getItemId() + " " + menuItem.getGroupId() + " " + menuItem.getOrder()); -// return false; -// } -// }); -// popupMenu.show(); -// } -//} diff --git a/app/src/main/java/me/wizos/loread/utils/ArticleUtil.java b/app/src/main/java/me/wizos/loread/utils/ArticleUtil.java new file mode 100644 index 0000000..48f3039 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/ArticleUtil.java @@ -0,0 +1,655 @@ +package me.wizos.loread.utils; + + +import android.text.Html; + +import com.socks.library.KLog; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.wizos.loread.App; +import me.wizos.loread.R; +import me.wizos.loread.bean.Enclosure; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.Feed; +import me.wizos.loread.extractor.ExtractorUtil; +import okhttp3.MediaType; +import okhttp3.ResponseBody; + +/** + * 文章处理工具类 + * + * @author by Wizos on 2020/3/16. + */ +public class ArticleUtil { + /** + * 将文章保存到内存 + * @param article + * @param title + * @return + */ + public static String getPageForSave(Article article, String title) { + String published = TimeUtil.format(article.getPubDate(), "yyyy-MM-dd HH:mm"); + String link = article.getLink(); + String content = getFormatContentForSave(title, article.getContent()); + Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), article.getFeedId()); + String author = getOptimizedAuthor(feed, article.getAuthor()); + + return "" + + "" + + "" + + "" + title + "" + + "" + + "

" + + "
" + + "

" + title + "

" + + "

" + author + "

" + + "

" + published + "

" + + "
" + + "
" + content + "
" + + "
" + + ""; + } + + +// public static String getPageForDisplay(Article article) { +// return getPageForDisplay(article, App.DISPLAY_RSS); +// } + public static String getPageForDisplay(Article article) { //, String referer + + if (null == article) { + return ""; + } + // 获取排版文件路径(支持自定义的文件) + String typesettingCssPath = App.i().getUserConfigPath() + "normalize.css"; + if (!new File(typesettingCssPath).exists()) { + typesettingCssPath = "file:///android_asset/css/normalize.css"; + } + + // 获取排版文件路径(支持自定义的文件) + String mediaJsPath = App.i().getUserConfigPath() + "media.js"; + if (!new File(mediaJsPath).exists()) { + mediaJsPath = "file:///android_asset/js/media.js"; + } + + // 获取主题文件路径 + String themeCssPath, hljCSssPath; + if (App.i().getUser().getThemeMode() == App.THEME_DAY) { + themeCssPath = App.i().getUserConfigPath() + "article_theme_day.css"; + if (!new File(typesettingCssPath).exists()) { + themeCssPath = "file:///android_asset/css/article_theme_day.css"; + } + } else { + themeCssPath = App.i().getUserConfigPath() + "article_theme_night.css"; + if (!new File(typesettingCssPath).exists()) { + themeCssPath = "file:///android_asset/css/article_theme_night.css"; + } + } + hljCSssPath = "file:///android_asset/css/android_studio.css"; + + Feed feed = CoreDB.i().feedDao().getById(App.i().getUser().getId(), article.getFeedId()); + String author = getOptimizedAuthor(feed, article.getAuthor()); + + String initImageHolderUrl = + "var IMAGE_HOLDER_CLICK_TO_LOAD_URL = placeholder.getData({text: '" + App.i().getString(R.string.click_to_load_this_picture) + "'});" + + "var IMAGE_HOLDER_LOADING_URL = placeholder.getData({text: '" + App.i().getString(R.string.loading) + "'});" + + "var IMAGE_HOLDER_LOAD_FAILED_URL = placeholder.getData({text: '" + App.i().getString(R.string.loading_failed_click_here_to_retry) + "'});" + + "var IMAGE_HOLDER_IMAGE_ERROR_URL = placeholder.getData({text: '" + App.i().getString(R.string.picture_error_click_here_to_retry) + "'});"; + String content = getFormatContentForDisplay2(article); + + String title = article.getTitle(); + if (StringUtils.isEmpty(title)) { + title = App.i().getString(R.string.no_title); + } + +// String readabilityButton = ""; +// String displayMode; +// if (feed != null) { +// displayMode = TestConfig.i().getDisplayMode(feed.getId()); +// } else { +// displayMode = App.DISPLAY_RSS; +// } + + // 默认展示rss时,提示“获取全文” + // 默认展示Readability,提示“本文已自动/手动排版,如有问题点击查看原文、修复规则” +// if (StringUtils.isEmpty(displayMode) || !App.DISPLAY_LINK.equals(displayMode)) { +// if (!App.DISPLAY_READABILITY.equals(referer)) { +// readabilityButton = "

获取全文"; +// } else { +// readabilityButton = "

恢复RSS内容"; +// } +// } + return "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + title + "" + + "" + + "
" + + "" + +// "
" + + "
" + content + +// readabilityButton + + "
" + + "
" + + "" + // defer + "" + + "" + + "" + + "" + + "" + + "" + + ""; + } + + + public static String getContentForSpeak(Article article) { + String html = article.getContent(); + Pattern pattern; + pattern = Pattern.compile("(
|
|

|

||
||
    |
      |
    1. )", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(". $1"); + + pattern = Pattern.compile("", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.summary_image)); + pattern = Pattern.compile("<(audio).*?>.*?", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.summary_audio)); + pattern = Pattern.compile("<(video).*?>.*?", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.summary_video)); + + html = Jsoup.parse(html).text(); + + // 将网址替换为 + pattern = Pattern.compile("https*://[\\w?-_=./&]*([\\s ]| |[^\\w?-_=./&]|$)", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.summary_link) + "$1"); + + // KLog.i("初始化内容" + html ); + return App.i().getString(R.string.article_title_is) + article.getTitle() + html.trim(); + } + + /** + * 获取修整后的概要 + * + * @param html 原文 + * @return + */ + public static String getOptimizedSummary(String html) { + if (StringUtils.isEmpty(html)) { + return html; + } + Pattern pattern; + + pattern = Pattern.compile("(
      |
      |

      |

      ||
      ||
        |
          |
        1. )", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll("➤$1"); + + pattern = Pattern.compile("", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.image)); + pattern = Pattern.compile("<(audio).*?>.*?", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.audio)); + pattern = Pattern.compile("<(video).*?>.*?", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.video)); + pattern = Pattern.compile("<(iframe).*?>.*?", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.frame)); + pattern = Pattern.compile("<(embed).*?>.*?", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(App.i().getString(R.string.frame)); + + html = Jsoup.parse(html).text(); + + // 将连续多个空格换为一个 + pattern = Pattern.compile("([\\s ]| ){2,}", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(" "); + + // 将连续多个➤合并为一个 + pattern = Pattern.compile("(([\\s ]| )*➤([\\s ]| )*){2,}", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll("➤"); + + // 将某些符号前后的去掉 + pattern = Pattern.compile("([\\s ]| )*➤+([\\s ]| )*([+_\\-=%@#$^&,,.。::!!??○●◎⊙☆★◇◆□■△▲〓\\[\\]“”()()〔〕〈〉《》「」『』[]〖〗【】{}])", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll("$3"); + pattern = Pattern.compile("([+_\\-=%@#$^&,,.。::!!??○●◎⊙☆★◇◆□■△▲〓\\[\\]“”()()〔〕〈〉《》「」『』[]〖〗【】{}])([\\s ]| )*➤+([\\s ]| )*", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll("$1"); + + // 将开头的去掉 + pattern = Pattern.compile("^\\s*➤*\\s*", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(""); + + // 将末尾的去掉 + pattern = Pattern.compile("\\s*➤*\\s*$", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(""); + + // 给前后增加空格 + pattern = Pattern.compile("([\\s ]| )*➤([\\s ]| )*", Pattern.CASE_INSENSITIVE); + html = pattern.matcher(html).replaceAll(" ➤ "); + + html = html.substring(0, Math.min(html.length(), 90) ); + return html.trim(); + } + + /** + * 优化标题,去掉html转义、换行符 + * @param title 文章标题 + * @return + */ + public static String getOptimizedTitle(String title) { + if (!StringUtils.isEmpty(title)) { + title = title.replace("\r", "").replace("\n", ""); + title = Html.fromHtml(Html.fromHtml(title).toString()).toString(); + return title; + } + return title; + } + + /** + * 在将服务器的文章入库前,对文章进行修整,主要是过滤无用&有干扰的标签、属性 + * @param articleUrl 文章链接 + * @param content 原文 + * @return + */ + public static String getOptimizedContent(String articleUrl, String content) { + if (StringUtils.isEmpty(content)) { + return content; + } + Pattern pattern; + Matcher matcher; + + // 过滤Ino广告 + pattern = Pattern.compile("(?=
          )[\\s\\S]*?inoreader[\\s\\S]*?(?<=
          )", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll(""); + + // 删除无效的空标签(无任何属性的) + pattern = Pattern.compile("([\\s ]| )*<([a-zA-Z0-9]{1,10})>([\\s ]| )*([\\s ]| )*", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll(""); + + // 删除无效的空标签(有属性的),注意此处的空标签必须是指定的,不然会把一些类似图片/音频/视频等“有意义的带属性空标签”给去掉 + pattern = Pattern.compile("([\\s ]| )*<(i|p|section|div|figure|pre|table|blockquote) [^>/]+>([\\s ]| )*([\\s ]| )*", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll(""); + + // 将包含
          的空标签给解脱出来 + pattern = Pattern.compile("([\\s ]| )*<([a-zA-Z0-9]{1,10})>([\\s ]| )*(
          )+([\\s ]| )*([\\s ]| )*", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll("
          "); + + // 将包含
          的空标签给解脱出来 + pattern = Pattern.compile("([\\s ]| )*<([a-zA-Z0-9]{1,10})>([\\s ]| )*(
          )+([\\s ]| )*([\\s ]| )*", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll("
          "); + + // 去掉没有意义的span标签 + pattern = Pattern.compile("[\\s ]*([\\s\\S]*?)[\\s ]*", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll("$1"); + + // 去掉块状元素之间的换行标签 + pattern = Pattern.compile("|p|div|figure|pre|img|audio|video|iframe|embed|table|blockquote)>([\\s ]| )*(
          )+([\\s ]| )*<(|p|div|figure|pre|img|audio|video|iframe|embed|table|blockquote)>", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll("<$5>"); + + // 去掉文章开头以及,各种【标题头】后紧跟着的换行标签 + pattern = Pattern.compile("(^||

          |

          |
          |
          |
          )([\\s ]| )*(
          |
          |)+([\\s ]| )*", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll("$1"); + + // 去掉文章末尾的换行标签 + pattern = Pattern.compile("([\\s ]| )*(
          |
          |)+([\\s ]| )*($||

          |
          |||)", Pattern.CASE_INSENSITIVE); + content = pattern.matcher(content).replaceAll("$4"); + + // 给两个连续的链接之间加一个换行符 + pattern = Pattern.compile("([\\s ]| )*
          0 && elements.first().nodeName().equalsIgnoreCase("header") ){ + elements.first().remove(); + } + + // 去掉script标签 + documentBody.getElementsByTag("script").remove(); + // 去掉style标签 + documentBody.getElementsByTag("style").remove(); + // tabindex属性,会导致图片有边框 + documentBody.removeAttr("tabindex"); + // video的controlslist属性可能会被配置为禁用下载/全屏,所以去掉 + documentBody.removeAttr("controlslist"); + documentBody.removeAttr("dragable"); + documentBody.removeAttr("contenteditable"); + // img的crossorigin属性导致图片无法正确展示 + documentBody.removeAttr("crossorigin"); + documentBody.removeAttr("class"); + + // 去掉src为空的标签 + documentBody.select("[src=''],iframe:not([src]),embed:not([src])").remove(); + + // 将 href 属性为空的 a 标签 unwrap + documentBody.select("[href=''],a:not([href])").unwrap(); + + // 将 noscript 标签 unwrap + documentBody.getElementsByTag("noscript").unwrap(); + //KLog.e("内容V:" + documentBody.html()); + String tmp; + // \s匹配的是 制表符\t,换行符\n,回车符\r,换页符\f以及半角空格 + elements = documentBody.getElementsByTag("pre"); + for (int i = 0, size = elements.size(); i < size; i++) { + tmp = elements.get(i).html().trim(); + pattern = Pattern.compile("([\\s ]| )*<([^>/]+)>([\\s ]| )*([\\s\\S]+)([\\s ]| )*([\\s ]| )*", Pattern.CASE_INSENSITIVE); + matcher = pattern.matcher(tmp); + if (matcher.matches()) { + tmp = pattern.matcher(tmp).replaceAll("<$2>$4"); + elements.get(i).html(tmp); + } + } + //KLog.e("内容D:" + documentBody.html()); + elements = documentBody.getElementsByTag("code"); + for (int i = 0, size = elements.size(); i < size; i++) { + tmp = elements.get(i).html().trim(); + pattern = Pattern.compile("([\\s ]| )*<([^>/]+)>([\\s ]| )*([\\s\\S]+)([\\s ]| )*([\\s ]| )*", Pattern.CASE_INSENSITIVE); + matcher = pattern.matcher(tmp); + if (matcher.matches()) { + tmp = pattern.matcher(tmp).replaceAll("<$2>$4"); + elements.get(i).html(tmp); + } + } + + // 将以下存放的原始src转为src的路径 + String[] oriSrcAttr = {"data-src", "data-original", "data-lazy-src", "zoomfile", "file"}; + for (String attr : oriSrcAttr) { + elements = documentBody.select("img[" + attr + "]"); + for (int i = 0, size = elements.size(); i < size; i++) { + element = elements.get(i); + tmp = element.attr(attr); + element.removeAttr(attr).attr("src", tmp); + } + } + + // picture 元素下会有一个标准的 img 元素,以及多个在不同条件下适配的 source 元素。 + // 故先将 source 元素去掉,再将 picture unwrap,仅保留 img 元素 + documentBody.select("picture > source").remove(); + documentBody.getElementsByTag("picture").unwrap(); + + // 只保留 srcset 属性中尺寸最大的一张图片(该属性会根据屏幕分辨率选择想要显示的src) + elements = documentBody.select("img[srcset]"); + for (int i = 0, size = elements.size(); i < size; i++) { + element = elements.get(i); + String srcsetAttr = element.attr("srcset"); + if (StringUtils.isEmpty(srcsetAttr)) { + continue; + } + String[] srcset; + if (srcsetAttr.contains(",")) { + srcset = srcsetAttr.split(","); + } else { + srcset = new String[]{srcsetAttr}; + } + int greaterDimen = 0; + String greaterSrc = null; + for (String srcDimen : srcset) { + pattern = Pattern.compile("(\\S+)\\s+(\\d*)[xXwW]", Pattern.CASE_INSENSITIVE); + matcher = pattern.matcher(srcDimen); + if (!matcher.find()) { + continue; + } + if (!StringUtils.isEmpty(matcher.group(2)) && Integer.parseInt(matcher.group(2)) > greaterDimen) { + greaterSrc = matcher.group(1); + greaterDimen = Integer.parseInt(matcher.group(2)); + } + } + if (!StringUtils.isEmpty(greaterSrc)) { + elements.get(i).attr("src", greaterSrc); + } + element.removeAttr("srcset").removeAttr("sizes"); + } + + // 去掉内联的css样式中的强制不换行 + elements = documentBody.select("[style*=white-space]"); + for (int i = 0, size = elements.size(); i < size; i++) { + tmp = elements.get(i).attr("style"); + pattern = Pattern.compile("white-space.*?(;|$)", Pattern.CASE_INSENSITIVE); + tmp = pattern.matcher(tmp).replaceAll(""); + elements.get(i).attr("style", tmp); + } + + // 将相对连接转为绝对链接 + elements = documentBody.getElementsByAttribute("src"); + for (int i = 0, size = elements.size(); i < size; i++) { + element = elements.get(i); + element.attr("src", element.attr("abs:src")); + } + elements = documentBody.getElementsByAttribute("href"); + for (int i = 0, size = elements.size(); i < size; i++) { + element = elements.get(i); + tmp = element.attr("href"); + if(StringUtils.isEmpty(tmp) || tmp.startsWith("magnet:?")){ + continue; + } + element.attr("href", element.attr("abs:href")); + } + + return documentBody.html().trim(); + } + + private static String getFormatContentForSave(String title, String content) { + Document document = Jsoup.parseBodyFragment(content); + Elements elements = document.getElementsByTag("img"); + String url, filePath; + for (int i = 0, size = elements.size(); i < size; i++) { + url = elements.get(i).attr("src"); + filePath = "./" + title + "_files/" + UriUtil.guessFileNameExt(url); + elements.get(i).attr("original-src", url); + elements.get(i).attr("src", filePath); + } + return document.body().html(); + } + + + /** + * 格式化给定的文本,用于展示 + * 这里没有直接将原始的文章内容给到 webView 加载,再去 webView 中初始化占位图并懒加载。 + * 是因为这样 WebView 刚启动时,有的图片因为还没有被 js 替换为占位图,而展示一个错误图。 + * 这里直接将内容初始化好,再让 WebView 执行懒加载的 js 去给没有加载本地图的 src 执行下载任务。 + * + * @param article + * @return + */ + private static String getFormatContentForDisplay2(Article article) { + if (StringUtils.isEmpty(article.getContent())) { + return ""; + } + String originalUrl; + String imgHolder = "file:///android_asset/image/image_holder.png"; + + Element img; + Document document = Jsoup.parseBodyFragment(article.getContent(), article.getLink()); + document = ColorModifier.i().inverseColor(document); + + Elements elements; + String cacheUrl; + String idInMD5 = EncryptUtil.MD5(article.getId()); + + elements = document.getElementsByTag("img"); + for (int i = 0, size = elements.size(); i < size; i++) { + img = elements.get(i); + // 抽取图片的绝对连接 + originalUrl = img.attr("abs:src"); + img.attr("original-src", originalUrl); + + cacheUrl = FileUtil.readCacheFilePath(idInMD5, originalUrl); + if (cacheUrl != null) { + img.attr("src", cacheUrl); + img.addClass("image-holder"); + } else { + img.attr("src", imgHolder); + } + } + + elements = document.getElementsByTag("input"); + for (Element element : elements) { + element.attr("disabled", "disabled"); + } + +// elements = document.getElementsByTag("video"); +// for (Element element : elements) { +// element.attr("controls", "true") +// .attr("width", "100%") +// .attr("height", "auto") +// .attr("preload", "metadata"); +// } + +// elements = document.getElementsByTag("audio"); +// for (Element element : elements) { +// element.attr("controls", "true") +// .attr("width", "100%"); +// } + +// // 给 table 包装 div 并配合 overflow-x: auto; ,让 table 内的 pre 不会撑出屏幕 +// elements = document.getElementsByTag("table"); +// for (int i = 0, size = elements.size(); i < size; i++) { +// elements.get(i).wrap("
          "); +// } + + return document.body().html().trim(); + } + public static String getCoverUrl(String articleUrl, String content) { + // 获取第1个图片作为封面 + Document document = Jsoup.parseBodyFragment(content,articleUrl); + Elements elements = document.getElementsByTag("img"); + String coverUrl = ""; + + if( elements != null && elements.size() > 0 ){ + for (Element element:elements) { + coverUrl = element.attr("abs:src"); + if(!coverUrl.endsWith(".svg")){ + break; + }else { + return coverUrl; + } + } + } + + + elements = document.select("video[poster]"); + if( elements != null && elements.size()>0 ){ + coverUrl = elements.attr("abs:poster"); + } + return coverUrl; + } + + + private static String getOptimizedAuthor(Feed feed, String articleAuthor) { + if (null == feed) { + if (StringUtils.isEmpty(articleAuthor)) { + return ""; + }else { + return articleAuthor; + } + } + + String articleAuthorLowerCase = StringUtils.isEmpty(articleAuthor) ? "" : articleAuthor.toLowerCase(); + String feedTitleLowerCase = StringUtils.isEmpty(feed.getTitle()) ? "" : feed.getTitle().toLowerCase(); + + if (feedTitleLowerCase.contains(articleAuthorLowerCase)) { + return feed.getTitle(); + } else if (articleAuthorLowerCase.contains(feedTitleLowerCase)) { + return articleAuthor; + } else { + return feed.getTitle() + " / " + articleAuthor; + } + } + + + public static String getOptimizedContentWithEnclosures(String content, List attachments){ + // 获取视频或者音频附件 + if (attachments != null && attachments.size() != 0) { + for (Enclosure enclosure : attachments) { + if (StringUtils.isEmpty(enclosure.getType()) || StringUtils.isEmpty(enclosure.getHref()) || content.contains(enclosure.getHref())) { + continue; + } + if (!StringUtils.isEmpty(content)){ + content = content + "
          "; + } + if (enclosure.getType().startsWith("image")) { + content = content + ""; + } else if (enclosure.getType().startsWith("audio")) { + content = content + ""; + } else if (enclosure.getType().startsWith("video")) { + content = content + ""; + } else if(enclosure.getType().equalsIgnoreCase("application/x-shockwave-flash")){ + content = content + ""; + } + } + } + return content; + } + + public static Article getReadabilityArticle(Article article, ResponseBody responseBody) throws IOException{ + MediaType mediaType = responseBody.contentType(); + String charset = null; + if( mediaType != null ){ + charset = DataUtil.getCharsetFromContentType(mediaType.toString()); + } + KLog.i("解析得到的编码为:" + mediaType + " , "+ charset ); + + // 换parser吧,jsoup默认使用是htmlParser,它会对返回内容做些改动来符合html规范,所以一般实际使用时都用的是xmlParser,代码如下 + Document doc; + try { + doc = Jsoup.parse(responseBody.byteStream(), charset, article.getLink()); + }catch (IOException e){ + throw e; + } + doc.outputSettings().prettyPrint(false); + String content = ExtractorUtil.getContent(article.getLink(), doc); + content = ArticleUtil.getOptimizedContent(article.getLink(), content); + //KLog.e("内容B:" + content); + Article newArticle = (Article)article.clone(); + newArticle.setContent(content); + + String summary = ArticleUtil.getOptimizedSummary(content); + newArticle.setSummary(summary); + + String coverUrl = ArticleUtil.getCoverUrl(article.getLink(), content); + + if(!StringUtils.isEmpty(coverUrl)){ + newArticle.setImage(coverUrl); + }else if( !StringUtils.isEmpty(article.getImage()) ){ + newArticle.setImage(null); + } + return newArticle; + } + + + + +// public static void autoSetArticleTags(Article article){ +// List categories = CoreDB.i().categoryDao().getByFeedId(article.getUid(),article.getFeedId()); +// List articleTags = CoreDB.i().articleTagDao().getByArticleId(article.getUid(),article.getId()); +// if(categories != null && categories.size() > 0 && (articleTags == null || articleTags.size() == 0)){ +// articleTags = new ArrayList<>(categories.size()); +// for (Category category:categories) { +// Tag tag = new Tag(); +// tag.setUid(article.getUid()); +// tag.setId(category.getTitle()); +// tag.setTitle(category.getTitle()); +// ArticleTag articleTag = new ArticleTag(article.getUid(),article.getId(),tag.getId()); +// articleTags.add(articleTag); +// } +// CoreDB.i().articleTagDao().insert(articleTags); +// ArticleTags.i().addArticleTags(articleTags); +// ArticleTags.i().save(); +// } +// } +} diff --git a/app/src/main/java/me/wizos/loread/utils/ColorModifier.java b/app/src/main/java/me/wizos/loread/utils/ColorModifier.java new file mode 100644 index 0000000..e6edd51 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/ColorModifier.java @@ -0,0 +1,727 @@ +package me.wizos.loread.utils; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.wizos.loread.App; + +/** + * Created by Wizos on 2019/6/7. + */ + +public class ColorModifier { + private static ColorModifier app; + + private ColorModifier() { + } + + public static ColorModifier i() { + if (app == null) { + synchronized (ColorModifier.class) { + if (app == null) { + app = new ColorModifier(); + } + } + } + return app; + } + + private Matcher m; + private String tmp = ""; + private int r = 0, g = 0, b = 0; + + public Document handleColor(Document doc) { + RGB backgroundColor; + if (App.i().getUser().getThemeMode() == App.THEME_DAY) { + backgroundColor = new RGB(255, 255, 255); + } else { + // return new RGB(69, 73, 82); + backgroundColor = new RGB(32, 43, 47); + } + + Elements elements; + doc.select("[bgcolor]").removeAttr("bgcolor"); + elements = doc.select("[style*=color]"); + for (Element element : elements) { + tmp = element.attr("style"); + // 去掉原生背景色 + m = Pattern.compile("background(\\s*:|-color).*?(;|$)", Pattern.CASE_INSENSITIVE).matcher(tmp); + tmp = m.replaceAll(""); + element.attr("style", tmp); + + // 优化前景色 + m = Pattern.compile("(^|\\s*)color\\s*:(.*?)($|;)", Pattern.CASE_INSENSITIVE).matcher(tmp); + if (m.find()) { + tmp = m.replaceFirst("color:" + modifyColor(m.group(2), backgroundColor) + ";"); + element.attr("style", tmp); + } + } + + elements = doc.select("[color]"); + for (Element element : elements) { + element.attr("style", "color:" + modifyColor(element.attr("color"), backgroundColor)); + element.removeAttr("color"); + } + return doc; + } + + public Document inverseColor(Document doc) { + if (App.i().getUser().getThemeMode() == App.THEME_DAY) { + return inverseColor(doc, new RGB(255, 255, 255)); + } else { + // return new RGB(69, 73, 82); + return inverseColor(doc, new RGB(32, 43, 47)); + } + } + + private Document inverseColor(Document doc, RGB backgroundColor) { + Elements elements; + doc.select("[bgcolor]").removeAttr("bgcolor"); + elements = doc.select("[style*=color]"); + for (Element element : elements) { + tmp = element.attr("style"); + // 先去掉背景色 + m = Pattern.compile("background(\\s*:|-color).*?(;|$)", Pattern.CASE_INSENSITIVE).matcher(tmp); + tmp = m.replaceAll(""); + element.attr("style", tmp); + + m = Pattern.compile("(^|\\s*)color\\s*:(.*?)($|;)", Pattern.CASE_INSENSITIVE).matcher(tmp); + if (m.find()) { + tmp = m.replaceFirst("color:" + modifyColor(m.group(2), backgroundColor) + ";"); + element.attr("style", tmp); + } + } + + elements = doc.select("[color]"); + for (Element element : elements) { + //element.attr("color",modifyColor(element.attr("color"),backgroundColor) ); + element.attr("style", "color:" + modifyColor(element.attr("color"), backgroundColor)); + element.removeAttr("color"); + } + return doc; + } + + private String modifyColor(String color, RGB backgroundColor) { + // 处理 RGB 颜色 + m = Pattern.compile("\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + r = Integer.valueOf(m.group(1)); + g = Integer.valueOf(m.group(2)); + b = Integer.valueOf(m.group(3)); + return modifyColor(backgroundColor, new RGB(r, g, b)); + } + + // 处理 #FF000000 类型的颜色 + m = Pattern.compile("#\\W*\\w{2}(\\w{6})\\W*$", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + r = Integer.parseInt(m.group(1).substring(0, 2), 16); + g = Integer.parseInt(m.group(1).substring(2, 4), 16); + b = Integer.parseInt(m.group(1).substring(4, 6), 16); + return modifyColor(backgroundColor, new RGB(r, g, b)); + } + + // 处理 #000000 类型的颜色 + m = Pattern.compile("#\\W*(\\w{6})\\W*$", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + r = Integer.parseInt(m.group(1).substring(0, 2), 16); + g = Integer.parseInt(m.group(1).substring(2, 4), 16); + b = Integer.parseInt(m.group(1).substring(4, 6), 16); + return modifyColor(backgroundColor, new RGB(r, g, b)); + } + + // 处理 #0F0 类型的颜色 + m = Pattern.compile("#\\W*(\\w{3})\\W*$", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + tmp = m.group(1).substring(0, 1); + r = Integer.parseInt(tmp + tmp, 16); + tmp = m.group(1).substring(1, 2); + g = Integer.parseInt(tmp + tmp, 16); + tmp = m.group(1).substring(2, 3); + b = Integer.parseInt(tmp + tmp, 16); + return modifyColor(backgroundColor, new RGB(r, g, b)); + } + + // 处理颜色 + m = Pattern.compile("LightPink", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 182, 193)); + } + m = Pattern.compile("Pink", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 192, 203)); + } + m = Pattern.compile("Crimson", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(220, 20, 60)); + } + m = Pattern.compile("LavenderBlush", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 240, 245)); + } + m = Pattern.compile("PaleVioletRed", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(219, 112, 147)); + } + m = Pattern.compile("HotPink", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 105, 180)); + } + m = Pattern.compile("DeepPink", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 20, 147)); + } + m = Pattern.compile("MediumVioletRed", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(199, 21, 133)); + } + m = Pattern.compile("Orchid", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(218, 112, 214)); + } + m = Pattern.compile("Thistle", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(216, 191, 216)); + } + m = Pattern.compile("plum", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(221, 160, 221)); + } + m = Pattern.compile("Violet", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(238, 130, 238)); + } + m = Pattern.compile("Magenta", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 0, 255)); + } + m = Pattern.compile("Fuchsia", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 0, 255)); + } + m = Pattern.compile("DarkMagenta", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(139, 0, 139)); + } + m = Pattern.compile("Purple", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(128, 0, 128)); + } + m = Pattern.compile("MediumOrchid", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(186, 85, 211)); + } + m = Pattern.compile("DarkVoilet", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(148, 0, 211)); + } + m = Pattern.compile("DarkOrchid", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(153, 50, 204)); + } + m = Pattern.compile("Indigo", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(75, 0, 130)); + } + m = Pattern.compile("BlueViolet", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(138, 43, 226)); + } + m = Pattern.compile("MediumPurple", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(147, 112, 219)); + } + m = Pattern.compile("MediumSlateBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(123, 104, 238)); + } + m = Pattern.compile("SlateBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(106, 90, 205)); + } + m = Pattern.compile("DarkSlateBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(72, 61, 139)); + } + m = Pattern.compile("Lavender", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(230, 230, 250)); + } + m = Pattern.compile("GhostWhite", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(248, 248, 255)); + } + m = Pattern.compile("Blue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 0, 255)); + } + m = Pattern.compile("MediumBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 0, 205)); + } + m = Pattern.compile("MidnightBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(25, 25, 112)); + } + m = Pattern.compile("DarkBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 0, 139)); + } + m = Pattern.compile("Navy", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 0, 128)); + } + m = Pattern.compile("RoyalBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(65, 105, 225)); + } + m = Pattern.compile("CornflowerBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(100, 149, 237)); + } + m = Pattern.compile("LightSteelBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(176, 196, 222)); + } + m = Pattern.compile("LightSlateGray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(119, 136, 153)); + } + m = Pattern.compile("SlateGray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(112, 128, 144)); + } + m = Pattern.compile("DodgerBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(30, 144, 255)); + } + m = Pattern.compile("AliceBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(240, 248, 255)); + } + m = Pattern.compile("SteelBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(70, 130, 180)); + } + m = Pattern.compile("LightSkyBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(135, 206, 250)); + } + m = Pattern.compile("SkyBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(135, 206, 235)); + } + m = Pattern.compile("DeepSkyBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 191, 255)); + } + m = Pattern.compile("LightBLue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(173, 216, 230)); + } + m = Pattern.compile("PowDerBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(176, 224, 230)); + } + m = Pattern.compile("CadetBlue", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(95, 158, 160)); + } + m = Pattern.compile("Azure", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(240, 255, 255)); + } + m = Pattern.compile("LightCyan", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(225, 255, 255)); + } + m = Pattern.compile("PaleTurquoise", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(175, 238, 238)); + } + m = Pattern.compile("Cyan", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 255, 255)); + } + m = Pattern.compile("Aqua", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 255, 255)); + } + m = Pattern.compile("DarkTurquoise", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 206, 209)); + } + m = Pattern.compile("DarkSlateGray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(47, 79, 79)); + } + m = Pattern.compile("DarkCyan", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 139, 139)); + } + m = Pattern.compile("Teal", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 128, 128)); + } + m = Pattern.compile("MediumTurquoise", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(72, 209, 204)); + } + m = Pattern.compile("LightSeaGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(32, 178, 170)); + } + m = Pattern.compile("Turquoise", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(64, 224, 208)); + } + m = Pattern.compile("BabyGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(127, 255, 170)); + } + m = Pattern.compile("MediumAquamarine", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 250, 154)); + } + m = Pattern.compile("MediumSpringGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(245, 255, 250)); + } + m = Pattern.compile("MintCream", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 255, 127)); + } + m = Pattern.compile("SpringGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(60, 179, 113)); + } + m = Pattern.compile("SeaGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(46, 139, 87)); + } + m = Pattern.compile("Honeydew", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(240, 255, 0)); + } + m = Pattern.compile("LightGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(144, 238, 144)); + } + m = Pattern.compile("PaleGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(152, 251, 152)); + } + m = Pattern.compile("DarkSeaGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(143, 188, 143)); + } + m = Pattern.compile("LimeGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(50, 205, 50)); + } + m = Pattern.compile("Lime", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 255, 0)); + } + m = Pattern.compile("ForestGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(34, 139, 34)); + } + m = Pattern.compile("Green", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 128, 0)); + } + m = Pattern.compile("DarkGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 100, 0)); + } + m = Pattern.compile("Chartreuse", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(127, 255, 0)); + } + m = Pattern.compile("LawnGreen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(124, 252, 0)); + } + m = Pattern.compile("GreenYellow", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(173, 255, 47)); + } + m = Pattern.compile("OliveDrab", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(85, 107, 47)); + } + m = Pattern.compile("Beige", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(107, 142, 35)); + } + m = Pattern.compile("LightGoldenrodYellow", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(250, 250, 210)); + } + m = Pattern.compile("Ivory", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 255, 240)); + } + m = Pattern.compile("LightYellow", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 255, 224)); + } + m = Pattern.compile("Yellow", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 255, 0)); + } + m = Pattern.compile("Olive", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(128, 128, 0)); + } + m = Pattern.compile("DarkKhaki", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(189, 183, 107)); + } + m = Pattern.compile("LemonChiffon", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 250, 205)); + } + m = Pattern.compile("PaleGodenrod", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(238, 232, 170)); + } + m = Pattern.compile("Khaki", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(240, 230, 140)); + } + m = Pattern.compile("Gold", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 215, 0)); + } + m = Pattern.compile("Cornislk", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 248, 220)); + } + m = Pattern.compile("GoldEnrod", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(218, 165, 32)); + } + m = Pattern.compile("FloralWhite", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 250, 240)); + } + m = Pattern.compile("OldLace", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(253, 245, 230)); + } + m = Pattern.compile("Wheat", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(245, 222, 179)); + } + m = Pattern.compile("Moccasin", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 228, 181)); + } + m = Pattern.compile("Orange", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 165, 0)); + } + m = Pattern.compile("PapayaWhip", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 239, 213)); + } + m = Pattern.compile("BlanchedAlmond", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 235, 205)); + } + m = Pattern.compile("NavajoWhite", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 222, 173)); + } + m = Pattern.compile("AntiqueWhite", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(250, 235, 215)); + } + m = Pattern.compile("Tan", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(210, 180, 140)); + } + m = Pattern.compile("BrulyWood", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(222, 184, 135)); + } + m = Pattern.compile("Bisque", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 228, 196)); + } + m = Pattern.compile("DarkOrange", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 140, 0)); + } + m = Pattern.compile("Linen", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(250, 240, 230)); + } + m = Pattern.compile("Peru", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(205, 133, 63)); + } + m = Pattern.compile("PeachPuff", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 218, 185)); + } + m = Pattern.compile("SandyBrown", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(244, 164, 96)); + } + m = Pattern.compile("Chocolate", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(210, 105, 30)); + } + m = Pattern.compile("SaddleBrown", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(139, 69, 19)); + } + m = Pattern.compile("SeaShell", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 245, 238)); + } + m = Pattern.compile("Sienna", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(160, 82, 45)); + } + m = Pattern.compile("LightSalmon", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 160, 122)); + } + m = Pattern.compile("Coral", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 127, 80)); + } + m = Pattern.compile("OrangeRed", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 69, 0)); + } + m = Pattern.compile("DarkSalmon", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(233, 150, 122)); + } + m = Pattern.compile("Tomato", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 99, 71)); + } + m = Pattern.compile("MistyRose", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 228, 225)); + } + m = Pattern.compile("Salmon", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(250, 128, 114)); + } + m = Pattern.compile("Snow", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 250, 250)); + } + m = Pattern.compile("LightCoral", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(240, 128, 128)); + } + m = Pattern.compile("RosyBrown", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(188, 143, 143)); + } + m = Pattern.compile("IndianRed", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(205, 92, 92)); + } + m = Pattern.compile("Red", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 0, 0)); + } + m = Pattern.compile("Brown", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(165, 42, 42)); + } + m = Pattern.compile("FireBrick", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(178, 34, 34)); + } + m = Pattern.compile("DarkRed", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(139, 0, 0)); + } + m = Pattern.compile("Maroon", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(128, 0, 0)); + } + m = Pattern.compile("White", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(255, 255, 255)); + } + m = Pattern.compile("WhiteSmoke", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(245, 245, 245)); + } + m = Pattern.compile("Gainsboro", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(220, 220, 220)); + } + m = Pattern.compile("LightGray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(211, 211, 211)); + } + m = Pattern.compile("Silver", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(192, 192, 192)); + } + m = Pattern.compile("DarkGray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(169, 169, 169)); + } + m = Pattern.compile("Gray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(128, 128, 128)); + } + m = Pattern.compile("DimGray", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(105, 105, 105)); + } + m = Pattern.compile("Black", Pattern.CASE_INSENSITIVE).matcher(color); + if (m.find()) { + return modifyColor(backgroundColor, new RGB(0, 0, 0)); + } + return color; + } + + /** + * 计算2个颜色的距离,若小于阈值则返回反色 + */ + private String modifyColor(RGB e1, RGB e2) { + // System.out.println("对比颜色:" + e1 + " = " +e2 ); + long rmean = ((long) e1.r + (long) e2.r) / 2; + long r = (long) e1.r - (long) e2.r; + long g = (long) e1.g - (long) e2.g; + long b = (long) e1.b - (long) e2.b; + // 黑白色距离约764.8,这里居中取382 + if (Math.sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8)) < 382) { + return "rgb(" + getInverseColor(e2.r) + ", " + getInverseColor(e2.g) + ", " + getInverseColor(e2.b) + ")"; +// return "#" + Integer.toHexString(getInverseColor(e2.r)) + Integer.toHexString(getInverseColor(e2.g)) + Integer.toHexString(getInverseColor(e2.b)); + } else { + return "rgb(" + e2.r + ", " + e2.g + ", " + e2.b + ")"; + } + } + + + // https://blog.csdn.net/do168/article/details/51619656 + private static int getInverseColor(int color) { + return 255 - color; + } + + private static int getInverseColor2(int color) { + if (color > 64 && color < 128) + color -= 64; + else if (color >= 128 && color < 192) + color += 64; + return 255 - color; + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/ColorUtil.java b/app/src/main/java/me/wizos/loread/utils/ColorUtil.java deleted file mode 100644 index e8f3cbf..0000000 --- a/app/src/main/java/me/wizos/loread/utils/ColorUtil.java +++ /dev/null @@ -1,130 +0,0 @@ -package me.wizos.loread.utils; - -import com.socks.library.KLog; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.wizos.loread.App; -import me.wizos.loread.data.WithPref; - -/** - * 屏幕工具 - * - * @author Wizos on 2016/2/13. - */ -public class ColorUtil { - - /** - * 计算2个颜色的距离(相似度) - */ - - private static double distance(RGB e1) { - RGB e2 = null; - if (WithPref.i().getThemeMode() == App.Theme_Day) { - e2 = new RGB(255, 255, 255); - } else { - e2 = new RGB(32, 43, 47); - } - return distance(e1, e2); - } - - private static double distance(RGB e1, RGB e2) { - long rmean = ((long) e1.r + (long) e2.r) / 2; - long r = (long) e1.r - (long) e2.r; - long g = (long) e1.g - (long) e2.g; - long b = (long) e1.b - (long) e2.b; - return Math.sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8)); - } - - private final static String P_BACKGROUND_COLOR = "background(\\s|:|-color).*?(;|$)"; - private final static String P_RGB = "(^|[^-])color:rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)"; - private final static String P_HEX8 = ":\\s*#\\s*[0-9a-zA-Z]{2}([0-9a-zA-Z]{6})\\s*(;|$)"; - private final static String P_HEX6 = ":\\s*#\\s*([0-9a-zA-Z]{6})\\s*(;|$)"; - private final static String P_HEX3 = ":\\s*#\\s*(\\d{3})\\s*(;|$)"; - - private final static int Color_Distance = 200; - - public static Document mod(Document doc) { - Elements elements = doc.select("[style*=color]"); -// KLog.e("颜色,获取到数量:" + elements.size()); - String style = null; - int r = 0, g = 0, b = 0; - double distance = 0; - Pattern pattern; - Matcher m; - for (Element element : elements) { - style = element.attr("style"); - KLog.e("获取到的style: " + style); - - // 先去掉背景色。在css中去掉? - pattern = Pattern.compile(P_BACKGROUND_COLOR, Pattern.CASE_INSENSITIVE); - m = pattern.matcher(style); - style = m.replaceAll(""); - - pattern = Pattern.compile(P_RGB, Pattern.CASE_INSENSITIVE); - m = pattern.matcher(style); - if (m.find()) { - r = Integer.valueOf(m.group(2)); - g = Integer.valueOf(m.group(3)); - b = Integer.valueOf(m.group(4)); - distance = ColorUtil.distance(new RGB(r, g, b)); - if (distance < Color_Distance) { - style = m.replaceFirst("rgb(" + (255 - r) + ", " + (255 - g) + ", " + (255 - b) + ")"); - } - } else { - pattern = Pattern.compile(P_HEX8, Pattern.CASE_INSENSITIVE); - m = pattern.matcher(style); - if (m.find()) { - r = Integer.parseInt(m.group(1).substring(0, 2), 16); - g = Integer.parseInt(m.group(1).substring(2, 4), 16); - b = Integer.parseInt(m.group(1).substring(4, 6), 16); - distance = ColorUtil.distance(new RGB(r, g, b)); - if (distance < Color_Distance) { - style = m.replaceFirst(":rgb(" + (255 - r) + ", " + (255 - g) + ", " + (255 - b) + ");"); - } - } else { - pattern = Pattern.compile(P_HEX6, Pattern.CASE_INSENSITIVE); - m = pattern.matcher(style); - if (m.find()) { - r = Integer.parseInt(m.group(1).substring(0, 2), 16); - g = Integer.parseInt(m.group(1).substring(2, 4), 16); - b = Integer.parseInt(m.group(1).substring(4, 6), 16); - distance = ColorUtil.distance(new RGB(r, g, b)); - if (distance < Color_Distance) { - style = m.replaceFirst(":rgb(" + (255 - r) + ", " + (255 - g) + ", " + (255 - b) + ");"); - } - } else { - pattern = Pattern.compile(P_HEX3, Pattern.CASE_INSENSITIVE); - m = pattern.matcher(style); - String tmp = ""; - if (m.find()) { - tmp = m.group(1).substring(0, 1); - r = Integer.parseInt(tmp + tmp, 16); - tmp = m.group(1).substring(1, 2); - g = Integer.parseInt(tmp + tmp, 16); - tmp = m.group(1).substring(2, 3); - b = Integer.parseInt(tmp + tmp, 16); - distance = ColorUtil.distance(new RGB(r, g, b)); - if (distance < Color_Distance) { - style = m.replaceFirst(":rgb(" + (255 - r) + ", " + (255 - g) + ", " + (255 - b) + ");"); - } - } - } - } - } - KLog.e(" 修改后的style: " + style); - KLog.e(" 颜色相似度为: " + distance); - element.attr("style", style); - } - KLog.e("=========================="); -// KLog.e( doc.outerHtml() ); - return doc; - } - - -} diff --git a/app/src/main/java/me/wizos/loread/utils/EncryptUtil.java b/app/src/main/java/me/wizos/loread/utils/EncryptUtil.java new file mode 100644 index 0000000..856cbca --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/EncryptUtil.java @@ -0,0 +1,34 @@ +package me.wizos.loread.utils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +/** + * 加密工具类 + * @author by Wizos on 2020/2/26. + */ +public class EncryptUtil { + /** + * 将字符串转成MD5值 + * + * @param string 字符串 + * @return MD5 后的字符串 + */ + public static String MD5(String string) { + byte[] hash; + try { + hash = MessageDigest.getInstance("MD5").digest(string.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + StringBuilder hex = new StringBuilder(hash.length * 2); + for (byte b : hash) { + if ((b & 0xFF) < 0x10) { + hex.append("0"); + } + hex.append(Integer.toHexString(b & 0xFF)); + } + return hex.toString(); + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/FileUtil.java b/app/src/main/java/me/wizos/loread/utils/FileUtil.java index 8b62c52..94f0d7a 100644 --- a/app/src/main/java/me/wizos/loread/utils/FileUtil.java +++ b/app/src/main/java/me/wizos/loread/utils/FileUtil.java @@ -1,10 +1,12 @@ package me.wizos.loread.utils; -import android.content.Context; +import android.Manifest; +import android.app.Activity; +import android.content.pm.PackageManager; import android.os.Environment; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.webkit.URLUtil; +import android.text.Html; + +import androidx.core.app.ActivityCompat; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -20,27 +22,24 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; -import java.util.Map; import me.wizos.loread.App; -import me.wizos.loread.data.WithDB; import me.wizos.loread.db.Article; -import me.wizos.loread.net.Api; +import me.wizos.loread.db.CoreDB; /** * @author Wizos on 2016/3/19. */ public class FileUtil { //判断外部存储(SD卡)是否可以读写 - private static boolean isExternalStorageWritable() { + public static boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } + //判断外部存储是否至少可以读 public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); @@ -49,7 +48,7 @@ public boolean isExternalStorageReadable() { public static void deleteHtmlDirList(ArrayList fileNameInMD5List) { - String externalCacheDir = App.externalFilesDir + "/cache/"; + String externalCacheDir = App.i().getUserFilesDir() + "/cache/"; for (String fileNameInMD5 : fileNameInMD5List) { // KLog.e("删除文件:" + externalCacheDir + fileNameInMD5 ); deleteHtmlDir(new File(externalCacheDir + fileNameInMD5)); @@ -59,51 +58,48 @@ public static void deleteHtmlDirList(ArrayList fileNameInMD5List) { /** * 递归删除应用下的缓存 + * * @param dir 需要删除的文件或者文件目录 * @return 文件是否删除 */ - public static boolean deleteHtmlDir( File dir ) { - if ( dir.isDirectory() ) { + public static boolean deleteHtmlDir(File dir) { + if (dir.isDirectory()) { // KLog.i( dir + "是文件夹"); File[] files = dir.listFiles(); for (File file : files) { - deleteHtmlDir( file ); + deleteHtmlDir(file); } return dir.delete(); // 删除目录 - }else{ + } else { // KLog.i( dir + "只是文件"); return dir.delete(); // 删除文件 } } - public static boolean moveFile(String srcFileName, String destFileName) { File srcFile = new File(srcFileName); - KLog.e("文件是否存在:" + srcFile.exists() + destFileName); + //KLog.i("文件是否存在:" + srcFile.exists() + destFileName); if (!srcFile.exists() || !srcFile.isFile()) { return false; } File destFile = new File(destFileName); -// if ( destFile.exists() && destFile.getParent()!=null){ -// destFile.getParentFile().mkdirs(); -// } - if (!destFile.getParentFile().exists()) { destFile.getParentFile().mkdirs(); } - return srcFile.renameTo( destFile ); + return srcFile.renameTo(destFile); } /** * 移动目录 - * @param srcDirName 源目录完整路径 - * @param destDirName 目的目录完整路径 + * + * @param srcDirName 源目录完整路径 + * @param destDirName 目的目录完整路径 * @return 目录移动成功返回true,否则返回false */ public static boolean moveDir(String srcDirName, String destDirName) { - KLog.i("移动文件夹a"); + //KLog.i("移动文件夹a"); File srcDir = new File(srcDirName); if (!srcDir.exists() || !srcDir.isDirectory()) { return false; @@ -113,7 +109,7 @@ public static boolean moveDir(String srcDirName, String destDirName) { if (!destDir.exists()) { destDir.mkdirs(); } - KLog.i("移动文件夹b"); + //KLog.i("移动文件夹b"); /** * 如果是文件则移动,否则递归移动文件夹。删除最终的空源文件夹 * 注意移动文件夹时保持文件夹的树状结构 @@ -129,119 +125,208 @@ public static boolean moveDir(String srcDirName, String destDirName) { return srcDir.delete(); } - public static void restore() { Gson gson = new Gson(); String content; - Article article; + Article tmp; - content = readFile(App.externalFilesDir + "/config/unreadIds-backup.json"); - List unreadIds = gson.fromJson(content, new TypeToken>() { - }.getType()); - if (unreadIds == null) { + content = readFile(App.i().getUserFilesDir() + "/config/articles-backup.json"); + List
          articles = gson.fromJson(content, new TypeToken>() {}.getType()); + if (articles == null) { return; } - List
          unreadArticles = new ArrayList<>(unreadIds.size()); - for (String id : unreadIds) { - article = WithDB.i().getArticle(id); - if (article == null) { + KLog.e("文豪A:" + articles.size() ); + List
          unreadArticles = new ArrayList<>(articles.size()); + for (Article article : articles) { + tmp = CoreDB.i().articleDao().getById(App.i().getUser().getId(), article.getId()); + if (tmp == null) { continue; } -// article.setReadState(Api.ART_UNREADING); - article.setReadStatus(Api.UNREADING); - unreadArticles.add(article); - } - WithDB.i().saveArticles(unreadArticles); - -// content = readFile(App.boxRelativePath + "staredIds-backup.json"); -// List staredIds = gson.fromJson(content,new TypeToken>() {}.getType()); -// List
          staredArticles = new ArrayList<>(staredIds.size()); -// for (String id: staredIds) { -// article = WithDB.i().getArticle(id); -// if(article==null){ -// continue; -// } -// article.setStarState(Api.ART_STARED); -// staredArticles.add(article); -// } -// WithDB.i().saveArticles(staredArticles); + tmp.setReadStatus(article.getReadStatus()); + tmp.setSaveStatus(article.getSaveStatus()); + unreadArticles.add(tmp); + } + CoreDB.i().articleDao().update(unreadArticles); } - public static void backup() { Gson gson = new Gson(); String content; - List
          articles = WithDB.i().getArtsUnreading(); - List unreadIds = new ArrayList<>(articles.size()); + List
          articles = CoreDB.i().articleDao().getBackup(App.i().getUser().getId()); + List
          backups = new ArrayList<>(articles.size()); + Article tmp; for (Article article : articles) { - unreadIds.add(article.getId()); - } - content = gson.toJson(unreadIds); - save(App.externalFilesDir + "/config/unreadIds-backup.json", content); - -// articles = WithDB.i().getArtsStared(); -// List staredIds = new ArrayList<>(articles.size()); -// for (Article article: articles) { -// staredIds.add(article.getId()); -// } -// content = gson.toJson(staredIds); -// save(App.storeRelativePath + "staredIds.backup",content); + tmp = new Article(); + tmp.setId(article.getId()); + tmp.setReadStatus(article.getReadStatus()); + tmp.setSaveStatus(article.getSaveStatus()); + backups.add(tmp); + } + content = gson.toJson(backups); + save(App.i().getUserFilesDir() + "/config/articles-backup.json", content); } - public static void saveArticle(String dir, Article article) { - String title = StringUtil.getOptimizedNameForSave(article.getTitle()); + String title = getSaveableName(article.getTitle()); String filePathTitle = dir + title; - String html = StringUtil.getPageForSave(article, title); + String html = ArticleUtil.getPageForSave(article, title); - String articleIdInMD5 = StringUtil.str2MD5(article.getId()); + String articleIdInMD5 = EncryptUtil.MD5(article.getId()); save(filePathTitle + ".html", html); - moveDir(App.externalFilesDir + "/cache/" + articleIdInMD5 + "/" + articleIdInMD5 + "_files", filePathTitle + "_files"); - moveDir(App.externalFilesDir + "/cache/" + articleIdInMD5 + "/original", filePathTitle + "_files"); + KLog.e("保存文件夹:" + filePathTitle + " , " + App.i().getUserFilesDir() + "/cache/" + articleIdInMD5 + "/original"); + moveDir(App.i().getUserFilesDir() + "/cache/" + articleIdInMD5 + "/original", filePathTitle + "_files"); + } + + + /** + * 处理文件名中的特殊字符和表情,用于保存为文件 + * + * @param fileName 文件名 + * @return 处理后的文件名 + */ + public static String getSaveableName(String fileName) { + // 因为有些title会用 html中的转义。所以这里要改过来 + fileName = Html.fromHtml(fileName).toString(); + fileName = SymbolUtil.filterEmoji(fileName); + fileName = SymbolUtil.filterUnsavedSymbol(fileName).trim(); + if (StringUtils.isEmpty(fileName)) { + fileName = TimeUtil.format(System.currentTimeMillis(),"yyyyMMddHHmmss"); + } else if (fileName.length() <= 2) { + fileName = fileName + TimeUtil.format(System.currentTimeMillis(),"_yyyyMMddHHmmss"); + } + return fileName.trim(); + } + + public static boolean saveText(String filePath, String fileContent, boolean append) { + if (!isExternalStorageWritable()) { + return false; + } + File file = new File(filePath); + + try { + if (file.exists()) { + if (!append) { + return false; + } + } else { + File folder = file.getParentFile(); + if (!folder.exists()) { + folder.mkdirs(); + } + } + +// KLog.d("【】" + file.toString() + "--"+ folder.toString()); + FileWriter fileWriter = new FileWriter(file, append); //在 (file,false) 后者表示在 fileWriter 对文件再次写入时,是否会在该文件的结尾续写,true 是续写,false 是覆盖。 + fileWriter.write(fileContent); + fileWriter.flush(); // 刷新该流中的缓冲。将缓冲区中的字符数据保存到目的文件中去。 + fileWriter.close(); // 关闭此流。在关闭前会先刷新此流的缓冲区。在关闭后,再写入或者刷新的话,会抛IOException异常。 + return true; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + public static void save(String filePath, String fileContent) { + if (!isExternalStorageWritable()) { + return; + } + File file = new File(filePath); + File folder = file.getParentFile(); + try { + if (folder != null && !folder.exists()) { + folder.mkdirs(); + } + KLog.i("保存规则:" + file.toString()); + FileWriter fileWriter = new FileWriter(file, false); //在 (file,false) 后者表示在 fileWriter 对文件再次写入时,是否会在该文件的结尾续写,true 是续写,false 是覆盖。 + fileWriter.write(fileContent); + fileWriter.flush(); // 刷新该流中的缓冲。将缓冲区中的字符数据保存到目的文件中去。 + fileWriter.close(); // 关闭此流。在关闭前会先刷新此流的缓冲区。在关闭后,再写入或者刷新的话,会抛IOException异常。 + } catch (IOException e) { + KLog.e("保存错误"); + e.printStackTrace(); + } + } + + public static void save(File file, String fileContent) throws IOException { + File folder = file.getParentFile(); + if (!folder.exists()) { + folder.mkdirs(); + } + KLog.e("【】" + file.toString() + "--" + folder.toString()); + FileWriter fileWriter = new FileWriter(file, false); //在 (file,false) 后者表示在 fileWriter 对文件再次写入时,是否会在该文件的结尾续写,true 是续写,false 是覆盖。 + fileWriter.write(fileContent); + fileWriter.flush(); // 刷新该流中的缓冲。将缓冲区中的字符数据保存到目的文件中去。 + fileWriter.close(); // 关闭此流。在关闭前会先刷新此流的缓冲区。在关闭后,再写入或者刷新的话,会抛IOException异常。 } public static String readFile(String filePath) { return readFile(new File(filePath)); } - + private static final int REQUEST_EXTERNAL_STORAGE = 1; + private static String[] PERMISSIONS_STORAGE = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + /** + * 在对sd卡进行读写操作之前调用这个方法 + * Checks if the app has permission to write to device storage + * If the app does not has permission then the user will be prompted to grant permissions + */ + public static void verifyStoragePermissions(Activity activity) { + // Check if we have write permission + int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); + } + } public static String readFile(File file) { - String fileContent ="" , temp = ""; + String fileContent = "", temp = ""; if (!file.exists()) { - return null; + return fileContent; } try { FileReader fileReader = new FileReader(file); - BufferedReader br = new BufferedReader( fileReader );//一行一行读取 。在电子书程序上经常会用到。 - while(( temp = br.readLine())!= null){ + BufferedReader br = new BufferedReader(fileReader);//一行一行读取 。在电子书程序上经常会用到。 + while ((temp = br.readLine()) != null) { fileContent += temp; // +"\r\n" } fileReader.close(); br.close(); - } catch (IOException e) { - e.printStackTrace(); - return null; + } catch (IOException f){ + f.printStackTrace(); } return fileContent; } - public static String readCacheFilePath(String articleIdInMD5, int index, String originalUrl) { + public static String readCacheFilePath(String articleIdInMD5, String originalUrl) { // 为了避免我自己来获取 FileNameExt 时,由于得到的结果是重复的而导致图片也获取到一致的。所以采用 base64 的方式加密 originalUrl,来保证唯一 - String fileNameExt, filePath, compressedFilePath, originalFilePath; - fileNameExt = index + "-" + StringUtil.getFileNameExtByUrl(originalUrl); - compressedFilePath = App.externalFilesDir + "/cache/" + articleIdInMD5 + "/compressed/" + fileNameExt; - if (new File(compressedFilePath).exists()) { - return compressedFilePath; + String fileNameExt, filePath; + fileNameExt = UriUtil.guessFileNameExt(originalUrl); + + // 推测该图片在保存时,由于src有问题,导致获取的文件名有重复时自动加上 hashCode 的机制 + filePath = App.i().getUserFilesDir() + "/cache/" + articleIdInMD5 + "/original/" + originalUrl.hashCode() + "_" + fileNameExt; + if (new File(filePath).exists()) { + return filePath; } - originalFilePath = App.externalFilesDir + "/cache/" + articleIdInMD5 + "/original/" + fileNameExt; - if (new File(originalFilePath).exists()) { - return originalFilePath; + filePath = App.i().getUserFilesDir() + "/cache/" + articleIdInMD5 + "/compressed/" + fileNameExt; + if (new File(filePath).exists()) { + return filePath; + } + + filePath = App.i().getUserFilesDir() + "/cache/" + articleIdInMD5 + "/original/" + fileNameExt; + if (new File(filePath).exists()) { + return filePath; } - filePath = App.externalFilesDir + "/cache/" + articleIdInMD5 + "/" + articleIdInMD5 + "_files/" + fileNameExt; + // 推测可能是svg格式的,该类文件必须有后缀名才能在webView中显示出来 + filePath = App.i().getUserFilesDir() + "/cache/" + articleIdInMD5 + "/original/" + fileNameExt + ".svg"; if (new File(filePath).exists()) { return filePath; } + // KLog.e("ImageBridge", "要读取的url:" + originalUrl + " 文件位置" + filePath); return null; } @@ -269,18 +354,6 @@ public static int copyFile(String fromFile, String toFile) { } } - -// /** -// * 根据文件路径拷贝文件 -// * -// * @param srcFile 源文件 -// * @param destPath 目标文件路径 -// * @return boolean 成功true、失败false -// */ -// public static boolean copyFile(File srcFile, String destPath) { -// return copyFile(srcFile, new File(destPath)); -// } - private static boolean copyFile(File srcFile, File destFile) { boolean result = false; if (!isExternalStorageWritable()) { @@ -337,71 +410,11 @@ public static boolean copyFileToPictures(File srcFile) { if (suffix.length() > 5) { suffix = getImageSuffix(srcFile); } - File destFile = new File(loreadDir.getAbsolutePath() + File.separator + TimeUtil.getCurrentDate("yyyyMMdd_HHmmss") + suffix); + File destFile = new File(loreadDir.getAbsolutePath() + File.separator + TimeUtil.format(System.currentTimeMillis(),"yyyyMMdd_HHmmss") + suffix); return copyFile(srcFile, destFile); } - public static void save(String filePath, String fileContent) { - if (!isExternalStorageWritable()) { - return; - } - File file = new File(filePath); - File folder = file.getParentFile(); - - try { - if (!folder.exists()) { - folder.mkdirs(); - } -// KLog.d("【】" + file.toString() + "--"+ folder.toString()); - FileWriter fileWriter = new FileWriter(file, false); //在 (file,false) 后者表示在 fileWriter 对文件再次写入时,是否会在该文件的结尾续写,true 是续写,false 是覆盖。 - fileWriter.write(fileContent); - fileWriter.flush(); // 刷新该流中的缓冲。将缓冲区中的字符数据保存到目的文件中去。 - fileWriter.close(); // 关闭此流。在关闭前会先刷新此流的缓冲区。在关闭后,再写入或者刷新的话,会抛IOException异常。 - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * @param dir 目录名称 - * @return 带有完整的相对路径的 path - * "/storage/emulated/0/Android/data/me.wizos.loread/files/" + Dir + "/" - */ - public static String getRelativeDir(String dir) { - return App.externalFilesDir + File.separator + dir + File.separator; - } - - public static String getAbsoluteDir(String dir) { - return "file://" + App.externalFilesDir + File.separator + dir + File.separator; - } - - - public static String guessDownloadFileName(String url, String contentDisposition, String mimeType) { - // 处理会把 epub 文件,识别为 bin 文件的 bug:https://blog.csdn.net/imesong/article/details/45568697 - String fileNameByGuess = URLUtil.guessFileName(url, contentDisposition, mimeType); - if ("application/octet-stream".equals(mimeType)) { - if (TextUtils.isEmpty(contentDisposition)) { - // 从路径中获取 - fileNameByGuess = url.substring(url.lastIndexOf("/") + 1); - } else { - fileNameByGuess = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9); - } -// int index = contentDisposition.indexOf("filename="); -// if( index != -1 ){ -// fileNameByGuess = contentDisposition.substring( index + 9 ); -// } - } - // 处理 url 中包含乱码中文的问题 - try { - fileNameByGuess = URLDecoder.decode(fileNameByGuess, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - - return fileNameByGuess; - } - private static String getImageSuffix(File imageFile) { try { @@ -441,131 +454,4 @@ private static String getImageSuffix(InputStream in) { return ".jpg"; } } - - - public static String guessImageSuffix(String url) { - int typeIndex = url.lastIndexOf("."); - String fileExt = url.substring(typeIndex, url.length()); - if(fileExt.contains(".jpg")){ - url = url.substring(0,typeIndex) + ".jpg"; - }else if(fileExt.contains(".jpeg")){ - url = url.substring(0,typeIndex) + ".jpeg"; - }else if(fileExt.contains(".png")){ - url = url.substring(0,typeIndex) + ".png"; - }else if(fileExt.contains(".gif")){ - url = url.substring(0,typeIndex) + ".gif"; - } - KLog.d( "【 修正后的url 】" + url ); - return url; - } - - - public static void clear(Context context) { - List
          articles = WithDB.i().getArtsAllNoOrder(); - Article article; - ArrayMap idsMap = new ArrayMap<>(articles.size()); - for (int i = 0, size = articles.size(); i < size; i++) { - article = articles.get(i); - idsMap.put(StringUtil.str2MD5(article.getId()), 1); - } - - File dir = new File(context.getExternalFilesDir(null) + "/cache/"); - KLog.e("数量:" + dir.listFiles().length); - File[] files = dir.listFiles(); - File file; - for (int i = 0, size = files.length; i < size; i++) { - file = files[i]; - if (idsMap.get(file.getName()) != null) { - idsMap.put(file.getName(), 2); - } - } - - for (Map.Entry entry : idsMap.entrySet()) { -// KLog.e("最终" + entry.getKey() ); - if (entry.getValue() == 2) { - KLog.e("获得文章标题:" + entry.getKey()); - deleteHtmlDir(new File(context.getExternalFilesDir(null) + "/cache/" + entry.getKey())); - } - } - } - - -// // 保存图片到手机指定目录 -// public static void saveBitmap(String filePath, String fileName, byte[] bytes) { -// // 判断SD卡是否存在,并且是否具有读写权限 -//// if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { -//// } -// FileOutputStream fos = null; -// try { -// File imgDir = new File(filePath); -// if (!imgDir.exists()) { -// imgDir.mkdirs(); -// } -// fileName = filePath + "/" + fileName; -// fos = new FileOutputStream(fileName); -// fos.write(bytes); -// } catch (IOException e) { -// e.printStackTrace(); -// } finally { -// try { -// if (fos != null) { -// fos.close(); -// } -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// } -// -// -// /** -// * 此方法在使用完InputStream后会关闭它。 -// * -// * @param is 输入流 -// * @param filePath 文件路径 -// * @throws IOException -// */ -// public static boolean saveFromStream( InputStream is, String filePath) throws IOException { -// KLog.e("saveFromStream", "当前线程为:" + Thread.currentThread().getId() + "--" + Thread.currentThread().getName() + "==" + filePath); -// File file = new File(filePath); -// BufferedInputStream bis = new BufferedInputStream(is); -// FileOutputStream os = null; -// BufferedOutputStream bos = null; // BufferedReader buffered = null.,故此时之关闭了in -// // TODO 保存文件,会遇到存储空间满的问题,如果批量保存文件,会一直尝试保存 -// try { -// File dir = file.getParentFile(); -// dir.mkdirs(); -// byte[] buff = new byte[8192]; -// /* -// * 这个取决于硬盘的扇区大小是512byte/sec,8192/512 = 16,表明写入了16扇区。write 在底层是调用scsi write (10)来写入数据的。 -// 这个可能协议有关,系统做了优化。所以你在写入数据的时候最好是512字节的倍数。 -// */ -// int size = 0; -// os = new FileOutputStream( file ); -// bos = new BufferedOutputStream(os); -// while ((size = bis.read(buff)) != -1) { // NullPointerException: Attempt to invoke virtual method 'okio.Segment okio.Segment.push(okio.Segment)' on a null object reference -// bos.write(buff, 0, size); -// } -// bos.flush(); -// return true; -// } finally { -// // 关闭通道使用close()方法,调用close()方法根据操作系统的网络实现不同可能会出现阻塞,可以在任何时候多次调用close();若出现阻塞,第一次调用close()后会一直等待; -// // 若第一次调用close()成功关闭后,之后再调用close()会立即返回,不会执行任何操作。 -// if (is != null) { -// is.close(); -// } -// if (bis != null) { -// bis.close(); -// } -// if (bos != null) { -// bos.close(); -// } -// if (os != null) { -// os.close(); -// } -//// return false; -// } -// } - - } diff --git a/app/src/main/java/me/wizos/loread/utils/ImageUtil.java b/app/src/main/java/me/wizos/loread/utils/ImageUtil.java new file mode 100644 index 0000000..2685281 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/ImageUtil.java @@ -0,0 +1,286 @@ +package me.wizos.loread.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.media.ThumbnailUtils; +import android.os.AsyncTask; + +import com.socks.library.KLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; + + +/** + * Created by Wizos on 2018/12/22. + */ +public class ImageUtil { + public interface OnMergeListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + void onSuccess(); + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + void onError(Throwable e); + } + + public static void mergeBitmap(Context context, final File bgFile, final OnMergeListener onMergeListener) { + mergeBitmap(new WeakReference(context), bgFile, onMergeListener); + } + + public static void mergeBitmap(final WeakReference context, final File bgFile, final OnMergeListener onMergeListener) { + if (!bgFile.getAbsolutePath().toLowerCase().endsWith(".gif") || !bgFile.getAbsolutePath().toLowerCase().contains("/compressed/")) { + onMergeListener.onSuccess(); + return; + } + AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + try { + FileInputStream fis1 = new FileInputStream(bgFile); + Bitmap bgBitmap = BitmapFactory.decodeStream(fis1).copy(Bitmap.Config.ARGB_8888, true); + + int shortSide = Math.min(bgBitmap.getWidth(), bgBitmap.getHeight()); + Bitmap fgBitmap = BitmapFactory.decodeStream(context.get().getAssets().open("image/gif_player.png")).copy(Bitmap.Config.ARGB_8888, true); + + if (shortSide < fgBitmap.getWidth()) { + onMergeListener.onSuccess(); + return; + } + Bitmap newBitmap = ImageUtil.mergeBitmap(bgBitmap, fgBitmap); + + //将合并后的bitmap3保存为png图片到本地 + FileOutputStream out = new FileOutputStream(bgFile.getAbsolutePath()); + newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + + out.close(); + + bgBitmap.recycle(); + bgBitmap = null; + fgBitmap.recycle(); + fgBitmap = null; + newBitmap.recycle(); + newBitmap = null; + onMergeListener.onSuccess(); + } catch (Exception e) { + + e.printStackTrace(); + KLog.e("报错"); + onMergeListener.onError(e); + } + } + }); + } + + + /** + * 作者:青青河边踩 + * 链接:https://www.jianshu.com/p/36fe123d973e + * 合成图片 + */ + public static Bitmap mergeBitmap(Bitmap bgBitmap, Bitmap fgBitmap) { +// BitmapFactory.Options options = new BitmapFactory.Options(); +// //只读取图片,不加载到内存中 +// options.inJustDecodeBounds = true; +// // isSampleSize是表示对图片的缩放程度,比如值为2图片的宽度和高度都变为以前的1/2 +// // inSampleSize只能是2的次方,如计算结果是7会按4进行压缩,计算结果是15会按8进行压缩。 +// options.inSampleSize = 1; + + //以其中一张图片的大小作为画布的大小,或者也可以自己自定义 + Bitmap newBitmap = Bitmap.createBitmap(bgBitmap); + //生成画布 + Canvas canvas = new Canvas(newBitmap); + + // 生成画笔 + Paint paint = new Paint(); + int w = bgBitmap.getWidth(); + int h = bgBitmap.getHeight(); + int w_2 = fgBitmap.getWidth(); + int h_2 = fgBitmap.getHeight(); + + float scale = (w * 0.2f) / w_2; + Matrix matrix = new Matrix(); + matrix.postScale(scale, scale); + fgBitmap = Bitmap.createBitmap(fgBitmap, 0, 0, w_2, h_2, matrix, true); + + // 设置第二张图片的位置 + canvas.drawBitmap(fgBitmap, (w - fgBitmap.getWidth()) / 2, (h - fgBitmap.getHeight()) / 2, paint); + canvas.save(); // Canvas.ALL_SAVE_FLAG +// bgBitmap.recycle(); +// bgBitmap = null; +// fgBitmap.recycle(); +// fgBitmap = null; + // 存储新合成的图片 + canvas.restore(); + return newBitmap; + } + + + public static void genPic(final File file, final File fileNew) { + AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + try { +// FileInputStream fileInputStream = new FileInputStream(App.i().getExUserFilesDir() + "/compressed/pic.jpg"); + + Bitmap newBitmap = getThumbnail(file, 1080); + + //将合并后的bitmap3保存为png图片到本地 + FileOutputStream out = new FileOutputStream(fileNew); + newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + KLog.e("成功获取 getThumbnail"); + } catch (Exception e) { + KLog.e("报错"); + e.printStackTrace(); + } + } + }); + } +// 作者:呵呵瓤儿 +// 链接:https://www.jianshu.com/p/cc61ea00f768 +// 來源:简书 +// 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。 + + /** + * 这样得到的图片会比原图体积大3倍 + * + * @param file + * @param screenWidth + * @return + * @throws IOException + */ + public static Bitmap getThumbnail(File file, int screenWidth) throws IOException { + BitmapFactory.Options options = new BitmapFactory.Options(); + //只读取图片,不加载到内存中 + options.inJustDecodeBounds = true; + options.inSampleSize = 1; + + BitmapFactory.decodeStream(new FileInputStream(file), null, options); + int imgWidth = options.outWidth; + int imgHeight = options.outHeight; + + KLog.e("宽高1=" + imgWidth + " " + imgHeight); + // 将长宽变为偶数 + imgWidth = imgWidth % 2 == 1 ? imgWidth + 1 : imgWidth; + imgHeight = imgHeight % 2 == 1 ? imgHeight + 1 : imgHeight; + screenWidth = screenWidth % 2 == 1 ? screenWidth + 1 : screenWidth; + + // 如果图片宽度大于屏幕宽度,则生成缩略图 + if (imgWidth > screenWidth) { + imgHeight = (int) (((float) screenWidth) / ((float) imgWidth) * imgHeight); + imgWidth = screenWidth; + } + return ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(file.getAbsolutePath()), imgWidth, imgHeight); + } + + /** + * 未检验过 + */ +// 作者:dream_monkey +// 原文:https://blog.csdn.net/dream_monkey/article/details/51461622 + public static Bitmap adjustImage(String absolutePath, int screenWidth, int screenHeight) { + BitmapFactory.Options opt = new BitmapFactory.Options(); + // 这个isjustdecodebounds很重要 + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(absolutePath, opt); + + // 获取到这个图片的原始宽度和高度 + int picWidth = opt.outWidth; +// int picHeight = opt.outHeight; + + // 将长宽变为偶数 + picWidth = picWidth % 2 == 1 ? picWidth + 1 : picWidth; + screenWidth = screenWidth % 2 == 1 ? screenWidth + 1 : screenWidth; +// picHeight = picHeight % 2 == 1 ? picHeight + 1 : picHeight; +// screenHeight = screenHeight % 2 == 1 ? screenHeight + 1 : screenHeight; + + // isSampleSize是表示对图片的缩放程度,比如值为2图片的宽度和高度都变为以前的1/2 + // inSampleSize只能是2的次方,如计算结果是7会按4进行压缩,计算结果是15会按8进行压缩。 + opt.inSampleSize = 1; + // 根据屏的大小和图片大小计算出缩放比例 + if (picWidth > screenWidth) + opt.inSampleSize = picWidth / screenWidth; + +// if (picWidth > picHeight) { +// if (picWidth > screenWidth) +// opt.inSampleSize = picWidth / screenWidth; +// } else { +// if (picHeight > screenHeight) +// opt.inSampleSize = picHeight / screenHeight; +// } + + // 这次再真正地生成一个有像素的,经过缩放了的bitmap + opt.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(absolutePath, opt); + } + + public static String getImageType(File srcFilePath) { + FileInputStream imgFile; + byte[] b = new byte[10]; + int l = -1; + try { + imgFile = new FileInputStream(srcFilePath); + l = imgFile.read(b); + imgFile.close(); + } catch (Exception e) { + return null; + } + if (l == 10) { + byte b0 = b[0]; + byte b1 = b[1]; + byte b2 = b[2]; + byte b3 = b[3]; + byte b6 = b[6]; + byte b7 = b[7]; + byte b8 = b[8]; + byte b9 = b[9]; + if (b0 == (byte) 'G' && b1 == (byte) 'I' && b2 == (byte) 'F') { + return "gif"; + } else if (b1 == (byte) 'P' && b2 == (byte) 'N' && b3 == (byte) 'G') { + return "png"; + } else if (b6 == (byte) 'J' && b7 == (byte) 'F' && b8 == (byte) 'I' && b9 == (byte) 'F') { + return "jpg"; + } else { + return null; + } + } else { + return null; + } + } + + public static boolean isImg(File file) { + try { + FileInputStream is = new FileInputStream(file); + byte[] src = new byte[28]; + is.read(src, 0, 28); + StringBuilder stringBuilder = new StringBuilder(""); + for (byte b : src) { + int v = b & 0xFF; + String hv = Integer.toHexString(v).toUpperCase(); + if (hv.length() < 2) { + stringBuilder.append(0); + } + stringBuilder.append(hv); + } + ImgFileType[] fileTypes = ImgFileType.values(); + for (ImgFileType fileType : fileTypes) { + if (stringBuilder.toString().startsWith(fileType.getValue())) { + return true; + } + } + }catch (IOException e){ + return false; + } + return false; + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/ImgFileType.java b/app/src/main/java/me/wizos/loread/utils/ImgFileType.java new file mode 100644 index 0000000..7df1cdc --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/ImgFileType.java @@ -0,0 +1,61 @@ +package me.wizos.loread.utils; + +/** + * Created by jiangzeyin on 2017/3/15. + */ +public enum ImgFileType { + + /** + * JPEG,JPG + */ + JPEG("FFD8FF", "jpg"), + + /** + * PNG + */ + PNG("89504E47", "png"), + + /** + * GIF + */ + GIF("47494638", "gif"), + + /** + * TIFF + */ + TIFF("49492A00"), + + /** + * Windows bitmap + */ + BMP("424D"), + + + WEBP("52494646"), + + /** + * svg,还有一种是xml文件头 + */ + SVG("3C73766720"); + + private String value = ""; + private String ext = ""; + + ImgFileType(String value) { + this.value = value; + } + + ImgFileType(String value, String ext) { + this(value); + this.ext = ext; + } + + public String getExt() { + return ext; + } + + public String getValue() { + return value; + } + +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/utils/InjectUtil.java b/app/src/main/java/me/wizos/loread/utils/InjectUtil.java deleted file mode 100644 index c8948e7..0000000 --- a/app/src/main/java/me/wizos/loread/utils/InjectUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.wizos.loread.utils; - -/** - * @author by Wizos on 2018/6/29. - */ - -public class InjectUtil { - /** - * 注入全屏Js,对不同的视频网站分析相应的全屏控件——class标识 - * - * @param url 加载的网页地址 - * @return 注入的js内容,若不是需要适配的网址则返回空javascript - */ - public static String fullScreenJsFun2(String url) { - String fullScreenFlags = null; - if (url.contains("letv")) { - fullScreenFlags = "hv_ico_screen"; - } else if (url.contains("youku")) { - fullScreenFlags = "x-zoomin"; - } else if (url.contains("bilibili")) { - fullScreenFlags = "icon-widescreen"; - } else if (url.contains("qq")) { -// fullScreenFlags = "txp_btn_fullscreen"; - fullScreenFlags = "tvp_fullscreen_button"; - } else if (url.contains("sohu")) { - fullScreenFlags = "x-fs-btn"; - } - if (null != fullScreenFlags) { - return "javascript:document.getElementsByClassName('" + fullScreenFlags + "')[0].addEventListener('click',function(){VideoBridge.toggleScreenOrientation();return false;});"; - } else { - return "javascript:"; - } - } - - public static String fullScreenJsFun(String url) { - String command = null; - if (url.contains("letv")) { - command = toggleScreenOrientationCommand("hv_ico_screen"); - } else if (url.contains("youku")) { - command = toggleScreenOrientationCommand("x-zoomin"); - } else if (url.contains("bilibili")) { - command = toggleScreenOrientationCommand("icon-widescreen"); - } else if (url.contains("qq")) { - command = toggleScreenOrientationCommand("txp_btn_fullscreen") + toggleScreenOrientationCommand("tvp_fullscreen_button"); - } else if (url.contains("sohu")) { - command = toggleScreenOrientationCommand("x-fs-btn"); - } - if (null != command) { - return "javascript:" + command; - } else { - return "javascript:"; - } - } - - private static String toggleScreenOrientationCommand(String fullScreenFlags) { - return "document.getElementsByClassName('" + fullScreenFlags + "')[0].addEventListener('click',function(){VideoBridge.toggleScreenOrientation();return false;});"; - } -} diff --git a/app/src/main/java/me/wizos/loread/utils/NetworkUtil.java b/app/src/main/java/me/wizos/loread/utils/NetworkUtil.java index f7bcb32..b39faa6 100644 --- a/app/src/main/java/me/wizos/loread/utils/NetworkUtil.java +++ b/app/src/main/java/me/wizos/loread/utils/NetworkUtil.java @@ -5,7 +5,6 @@ import android.net.NetworkInfo; import me.wizos.loread.App; -import me.wizos.loread.data.WithPref; /** @@ -16,7 +15,7 @@ public class NetworkUtil { public static final int NETWORK_NONE = 0; public static final int NETWORK_MOBILE = 1; public static final int NETWORK_WIFI = 2; - public static int THE_NETWORK = 0; + private static int THE_NETWORK = 0; /** * 判断网络的状态 @@ -26,12 +25,14 @@ public static int getNetWorkState() { NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { if (networkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) { - return NETWORK_WIFI; + THE_NETWORK = NETWORK_WIFI; } else if (networkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) { - return NETWORK_MOBILE; + THE_NETWORK = NETWORK_MOBILE; } + } else { + THE_NETWORK = NETWORK_NONE; } - return NETWORK_NONE; + return THE_NETWORK; } public static boolean isNetworkAvailable() { @@ -42,64 +43,16 @@ public static boolean isWiFiUsed() { return THE_NETWORK == NETWORK_WIFI; } + public static void setTheNetwork(int network){ + THE_NETWORK = network; + } public static boolean canDownImg() { - // 网络不可用 if (!isNetworkAvailable()) { return false; } // 开启了仅Wifi情况下载,但是不处于wifi状态 - return !(WithPref.i().isDownImgOnlyWifi() && THE_NETWORK != NETWORK_WIFI); + return !(App.i().getUser().isDownloadImgOnlyWifi() && !isWiFiUsed()); } -// public static boolean canDownImg1() { -// if (!WithPref.i().isDownImgOnlyWifi() && !NetworkUtil.isNetworkAvailable()) { -// return false; -// } -// return !(WithPref.i().isDownImgOnlyWifi() && !NetworkUtil.isWiFiUsed()); -// } - - -// /** -// * 老版本 -// * 判断WIFI是否可用 -// */ -// public static boolean isWiFiUsed2() { -// ConnectivityManager connectivity = (ConnectivityManager) App.i().getSystemService(Context.CONNECTIVITY_SERVICE); -// if (connectivity == null) { -// return false; -// } -// if (Build.VERSION.SDK_INT >= 23) { -// KLog.d("Wifi", ">=23"); -// Network[] networks = connectivity.getAllNetworks(); -// if (networks == null) { -// return false; -// } -// NetworkInfo networkInfo; -// for (Network network : networks) { -// networkInfo = connectivity.getNetworkInfo(network); -//// return wifiNetworkIsAvailable( networkInfo ); -// if (networkInfo.getType() != ConnectivityManager.TYPE_WIFI) continue; -// KLog.e("Wifi==", networkInfo.isConnected()); -// return networkInfo.isConnected(); -// } -// } else { -// KLog.d("Wifi", "<23"); -// NetworkInfo[] networkInfos = connectivity.getAllNetworkInfo(); -// if (networkInfos == null) { -// return false; -// } -// for (NetworkInfo networkInfo : networkInfos) { -// //此处请务必使用NetworkInfo对象下的isAvailable()方法,isConnected()是检测当前是否连接到了wifi -// if (networkInfo.getType() != ConnectivityManager.TYPE_WIFI) { -// continue; -// } -// KLog.i("Wifi==", networkInfo.isConnected()); -// return networkInfo.isConnected(); -// } -// } -// return true; -// } - - } \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/utils/RGB.java b/app/src/main/java/me/wizos/loread/utils/RGB.java index a208be5..47ef240 100644 --- a/app/src/main/java/me/wizos/loread/utils/RGB.java +++ b/app/src/main/java/me/wizos/loread/utils/RGB.java @@ -14,4 +14,13 @@ public RGB(int r, int g, int b) { this.g = g; this.b = b; } + + @Override + public String toString() { + return "RGB{" + + "r=" + r + + ", g=" + g + + ", b=" + b + + '}'; + } } diff --git a/app/src/main/java/me/wizos/loread/utils/ResUtil.java b/app/src/main/java/me/wizos/loread/utils/ResUtil.java deleted file mode 100644 index 93490cc..0000000 --- a/app/src/main/java/me/wizos/loread/utils/ResUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -package me.wizos.loread.utils; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorRes; -import android.support.v4.content.ContextCompat; -import android.util.TypedValue; - -import me.wizos.loread.App; - -/** - * Created by Wizos on 2018/3/21. - */ - -public class ResUtil { - public static int getColor(@ColorRes int id) { - return App.i().getResources().getColor(id); - } - - - public static int resolveColor(Context context, @AttrRes int attr, int fallback) { - TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); - try { - return a.getColor(0, fallback); - } finally { - a.recycle(); - } - } - - - public static float getAttrFloatValue(Context context, @AttrRes int attrRes) { - TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(attrRes, typedValue, true); - return typedValue.getFloat(); - } - - public static int getAttrColor(Context context, @AttrRes int attrRes) { - TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(attrRes, typedValue, true); - return typedValue.data; - } - - public static ColorStateList getAttrColorStateList(Context context, @AttrRes int attrRes) { - TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(attrRes, typedValue, true); - return ContextCompat.getColorStateList(context, typedValue.resourceId); - } - - public static Drawable getAttrDrawable(Context context, int attrRes) { - int[] attrs = new int[]{attrRes}; - TypedArray ta = context.obtainStyledAttributes(attrs); - Drawable drawable = ta.getDrawable(0); - ta.recycle(); - return drawable; - } - -// public static int getAttrDimen(Context context, int attrRes){ -// TypedValue typedValue = new TypedValue(); -// context.getTheme().resolveAttribute(attrRes, typedValue, true); -// return TypedValue.complexToDimensionPixelSize(typedValue.data, QMUIDisplayHelper.getDisplayMetrics(context)); -// } -} diff --git a/app/src/main/java/me/wizos/loread/utils/ScreenUtil.java b/app/src/main/java/me/wizos/loread/utils/ScreenUtil.java index 8e8d73f..e6abd26 100644 --- a/app/src/main/java/me/wizos/loread/utils/ScreenUtil.java +++ b/app/src/main/java/me/wizos/loread/utils/ScreenUtil.java @@ -6,8 +6,6 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.os.Build; -import android.support.annotation.DimenRes; -import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; @@ -16,11 +14,16 @@ import android.view.ViewGroup; import android.view.WindowManager; +import androidx.annotation.DimenRes; +import androidx.appcompat.app.AppCompatActivity; + import me.wizos.loread.App; +import me.wizos.loread.R; /** * 屏幕工具 + * * @author Wizos on 2016/2/13. */ public class ScreenUtil { @@ -28,13 +31,14 @@ public class ScreenUtil { /** * 从 R.dimen 文件中获取到数值,再根据手机的分辨率转成为 px(像素) */ - public static int get2Px(Context context,@DimenRes int id) { + public static int get2Px(Context context, @DimenRes int id) { final float scale = context.getResources().getDisplayMetrics().density; - final float dpValue = (int)context.getResources().getDimension(id); + final float dpValue = (int) context.getResources().getDimension(id); return (int) (dpValue * scale + 0.5f); } - public static int getDimen(Context context,@DimenRes int id) { - return (int)context.getResources().getDimension(id); + + public static int getDimen(Context context, @DimenRes int id) { + return (int) context.getResources().getDimension(id); } /** @@ -105,6 +109,7 @@ public static int getScreenHeight(Context context) { /** * 获取屏幕内容高度 + * * @param activity * @return */ @@ -121,6 +126,7 @@ public static int getScreenHeight2(Activity activity) { /** * 获得状态栏的高度 + * * @param context * @return mStatusHeight */ @@ -144,6 +150,7 @@ public static int getStatusHeight(Context context) { /** * 获取当前屏幕截图,不包含状态栏 + * * @param activity * @return bp */ @@ -189,16 +196,16 @@ public static int getActionBarHeight(Context context) { return actionBarHeight; } final TypedValue tv = new TypedValue(); - if (context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, tv, true)) { - if (context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, tv, true)) { + if (context.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) { + if (context.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { + if (context.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); } } else { - if (context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, tv, true)) { + if (context.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); } } diff --git a/app/src/main/java/me/wizos/loread/utils/ScriptUtil.java b/app/src/main/java/me/wizos/loread/utils/ScriptUtil.java new file mode 100644 index 0000000..a7bfb36 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/ScriptUtil.java @@ -0,0 +1,45 @@ +package me.wizos.loread.utils; + +import com.socks.library.KLog; + +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + + +public class ScriptUtil { + private static ScriptUtil instance; + private static ScriptEngine engine; + private ScriptUtil() { } + + public static synchronized ScriptUtil init() { + if (instance == null) { + synchronized (ScriptUtil.class) { + if (instance == null) { + instance = new ScriptUtil(); + engine = new ScriptEngineManager().getEngineByName("rhino"); + } + } + } + return instance; + } + + public static ScriptUtil i(){ + if( instance == null ){ + init(); + } + return instance; + } + + public boolean eval(String js, Bindings bindings){ + try { + engine.eval(js, bindings); + return true; + } catch (ScriptException e) { + KLog.e("脚本执行错误" + e.getMessage() + "," +e.getFileName() + ","+ e.getColumnNumber() + "," + e.getLineNumber() ); + e.printStackTrace(); + return false; + } + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/SnackbarUtil.java b/app/src/main/java/me/wizos/loread/utils/SnackbarUtil.java index c69c660..9fbae78 100644 --- a/app/src/main/java/me/wizos/loread/utils/SnackbarUtil.java +++ b/app/src/main/java/me/wizos/loread/utils/SnackbarUtil.java @@ -5,13 +5,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; -import android.support.v4.widget.Space; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -22,6 +15,15 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.legacy.widget.Space; + +import com.google.android.material.snackbar.Snackbar; + import java.lang.ref.WeakReference; import me.wizos.loread.R; @@ -194,6 +196,9 @@ public static SnackbarUtil Short(View view, String message) { public static SnackbarUtil Long(View view, String message) { return new SnackbarUtil(new WeakReference(Snackbar.make(view, message, Snackbar.LENGTH_LONG))).backColor(0XFF323232); } + public static SnackbarUtil Long(View view,View anchorView, String message) { + return new SnackbarUtil(new WeakReference(Snackbar.make(view, message, Snackbar.LENGTH_LONG).setAnchorView(anchorView))).backColor(0XFF323232); + } /** * 初始化Snackbar实例 diff --git a/app/src/main/java/me/wizos/loread/utils/StringJoiner.java b/app/src/main/java/me/wizos/loread/utils/StringJoiner.java new file mode 100644 index 0000000..5bdde11 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/StringJoiner.java @@ -0,0 +1,80 @@ +package me.wizos.loread.utils; + + +import androidx.annotation.NonNull; + +import java.util.Objects; + +public class StringJoiner { + private String emptyValue; + // 前缀 + private final String prefix; + // 分隔符 + private final String delimiter; + // 后缀 + private final String suffix; + // 值 + private StringBuilder value; + + /** + * 构造器 + */ + public StringJoiner(CharSequence delimiter) { + this(delimiter, "", ""); + } + + public StringJoiner(CharSequence delimiter, + CharSequence prefix, + CharSequence suffix) { + Objects.requireNonNull(prefix, "The prefix must not be null"); + Objects.requireNonNull(delimiter, "The delimiter must not be null"); + Objects.requireNonNull(suffix, "The suffix must not be null"); + // make defensive copies of arguments + this.prefix = prefix.toString(); + this.delimiter = delimiter.toString(); + this.suffix = suffix.toString(); + this.emptyValue = this.prefix + this.suffix; + } + + // 拼接 + public StringJoiner add(CharSequence newElement) { + prepareBuilder().append(newElement); + return this; + } + + // 预拼接value + private StringBuilder prepareBuilder() { + // value已加前缀 + if (value != null) { + // 此时添加分隔符 + value.append(delimiter); + } else { + // value未加前缀时需要先添加前缀 + value = new StringBuilder().append(prefix); + } + return value; + } + + //重写了toString 方法 + @NonNull + @Override + public String toString() { + if (value == null) { + // value未进行任何字符拼接时反悔emptyValue + return emptyValue; + } else { + // 后缀为""字符时,直接返回value + if (suffix.equals("")) { + return value.toString(); + } else { + // 获取value未拼接后缀的长度 + int initialLength = value.length(); + String result = value.append(suffix).toString(); + // reset value to pre-append initialLength + // 此处是为了保证value.toString()为未拼接后缀前的字符串 + value.setLength(initialLength); + return result; + } + } + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/StringUtil.java b/app/src/main/java/me/wizos/loread/utils/StringUtil.java deleted file mode 100644 index 70fab68..0000000 --- a/app/src/main/java/me/wizos/loread/utils/StringUtil.java +++ /dev/null @@ -1,643 +0,0 @@ -package me.wizos.loread.utils; - - -import android.text.Html; -import android.text.TextUtils; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.wizos.loread.App; -import me.wizos.loread.bean.config.GlobalConfig; -import me.wizos.loread.data.WithDB; -import me.wizos.loread.data.WithPref; -import me.wizos.loread.db.Article; -import me.wizos.loread.db.Feed; -import me.wizos.loread.net.Api; - -/** - * 字符处理工具类 - * @author by Wizos on 2016/3/16. - */ -public class StringUtil { - - public static String toLongID(String id) { - id = Long.toHexString(Long.valueOf(id)); - return "tag:google.com,2005:reader/item/" + String.format("%0" + (16 - id.length()) + "d", 0) + id; - } - - /** - * 将字符串转成MD5值 - * - * @param string 字符串 - * @return MD5 后的字符串 - */ - public static String str2MD5(String string) { - byte[] hash; - try { - hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8")); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - return null; - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - StringBuilder hex = new StringBuilder(hash.length * 2); - for (byte b : hash) { - if ((b & 0xFF) < 0x10) { - hex.append("0"); - } - hex.append(Integer.toHexString(b & 0xFF)); - } - return hex.toString(); - } - - - - public static boolean isBlank(List list){return list==null || list.isEmpty() || list.size()==0;} - - - private static String getFormatContentForSave(String title, String content) { - Document document = Jsoup.parseBodyFragment(content); - Elements elements = document.getElementsByTag("img"); - String url, filePath; - for (int i = 0, size = elements.size(); i < size; i++) { - url = elements.get(i).attr("src"); - filePath = "./" + title + "_files/" + i + "-" + getFileNameExtByUrl(url); - elements.get(i).attr("original-src", url); - elements.get(i).attr("src", filePath); - } - return document.body().html(); - } - - - - /** - * 格式化给定的文本,用于展示 - * 这里没有直接将原始的文章内容给到 webView 加载,再去 webView 中初始化占位图并懒加载。 - * 是因为这样 WebView 刚启动时,有的图片因为还没有被 js 替换为占位图,而展示一个错误图。 - * 这里直接将内容初始化好,再让 WebView 执行懒加载的 js 去给没有加载本地图的 src 执行下载任务。 - * @param articleID - * @param content - * @return - */ - private static String getFormatContentForDisplay(String articleID, String articleUrl, String content) { - if (TextUtils.isEmpty(content)) { - return ""; - } - String cacheUrl; - String originalUrl; - String imageHolder; - // 没有网络 - if (!NetworkUtil.isNetworkAvailable()) { - imageHolder = "file:///android_asset/image/image_holder_load_failed.png"; - } - // 开启省流量,蜂窝模式 - else if (WithPref.i().isDownImgOnlyWifi() && !NetworkUtil.isWiFiUsed()) { - imageHolder = "file:///android_asset/image/image_holder_click_to_load.png"; - } else { - imageHolder = "file:///android_asset/image/image_holder_loading.png"; - } - - Element img; - Document document = Jsoup.parseBodyFragment(content, articleUrl); - document = ColorUtil.mod(document); -// KLog.e("优化后的文本A:" + document.outerHtml() ); - // 去掉src为空的标签 - document.select("[src='']").remove(); -// KLog.e("内容链接为:" + document.outerHtml() ); - - // 将data-src转为src的路径 - Elements imgs_datasrc = document.select("img[data-src]"); - for (int i = 0, size = imgs_datasrc.size(); i < size; i++) { - img = imgs_datasrc.get(i); - originalUrl = img.attr("data-src"); - img.attr("src", originalUrl); - } - - - // 将相对连接转为绝对链接 - Elements hrefs = document.select("[href]"); - Element link; - for (int i = 0, size = hrefs.size(); i < size; i++) { - link = hrefs.get(i); - link.attr("href", link.attr("abs:href")); - } - - Elements srcs = document.select("[src]"); - Element src; - for (int i = 0, size = srcs.size(); i < size; i++) { - src = srcs.get(i); - src.attr("src", src.attr("abs:src")); - } - - - Elements imgs = document.getElementsByTag("img"); - String idInMD5 = StringUtil.str2MD5(articleID); - for (int i = 0, size = imgs.size(); i < size; i++) { - img = imgs.get(i); - // 抽取图片的绝对连接 - originalUrl = img.attr("abs:src"); - img.attr("original-src", originalUrl); - cacheUrl = FileUtil.readCacheFilePath(idInMD5, i, originalUrl); - if (cacheUrl != null) { - img.attr("src", cacheUrl); - } else { - img.attr("src", imageHolder); - } - img.attr("width", "100%"); - img.attr("height", "auto"); - } - - Elements videos = document.getElementsByTag("video"); - for (int i = 0, size = videos.size(); i < size; i++) { -// videos.get(i).attr("class", "video-js vjs-default-skin vjs-fluid vjs-big-play-centered"); -// videos.get(i).attr("data-setup", "{}"); -// videos.get(i).attr("style", "width:100%;height:100%"); - videos.get(i).attr("preload", "metadata"); - videos.get(i).attr("controls", "true"); - videos.get(i).attr("width", "100%"); - videos.get(i).attr("height", "auto"); - } - - Elements audios = document.getElementsByTag("audio"); - for (int i = 0, size = audios.size(); i < size; i++) { - audios.get(i).attr("controls", "true"); - audios.get(i).attr("width", "100%"); - } - - // 在 iframe 的外层加一个相对路径的 div,便于在js中给 iframe 加一个绝对位置的蒙层 - Elements iframes = document.getElementsByTag("iframe"); - Element iframe; - for (int i = 0, size = iframes.size(); i < size; i++) { - iframe = iframes.get(i); - if (!iframe.hasAttr("src")) { - iframe.remove(); - } else { - iframe.wrap("
          "); - } - // 阻止iframe里引用的网页自动跳转,会导致视频的缩略图无法显示 -// iframe.attr("sandbox",""); - } - - Elements embeds = document.getElementsByTag("embed"); - Element embed; - for (int i = 0, size = embeds.size(); i < size; i++) { - embed = embeds.get(i); - if (!embed.hasAttr("src")) { - embed.remove(); - } else { - embed.wrap("
          "); - } - } - - content = document.body().html().trim(); -// KLog.e("优化后的文本B:" + content ); - return content; - } - - -// private static String keyword; -// public static String getKeyWord(Node node) { -// if (node instanceof Element) { -// Element tag = (Element) node; -// -// for (Node childNode : tag.childNodes()) { -// KLog.e("====循环猜测"); -// keyword = getKeyWord(childNode); -// if( !TextUtils.isEmpty(keyword) ){ -// return keyword; -// } -//// return getKeyWord(childNode); -// } -// KLog.e("猜关键字是1:" + tag.text() ); -// return tag.text(); -// } else if (node instanceof TextNode) { -// TextNode tn = (TextNode) node; -// KLog.e("猜关键字是2:" + tn.text() ); -// return tn.text(); -// } else { -// return ""; -// } -// } - - private static String getOptimizedAuthor(String feedTitle, String articleAuthor) { - if (TextUtils.isEmpty(articleAuthor) || feedTitle.toLowerCase().contains(articleAuthor.toLowerCase())) { - return feedTitle; - } else if (articleAuthor.toLowerCase().contains(feedTitle.toLowerCase())) { - return articleAuthor; - } else { - return feedTitle + "@" + articleAuthor; - } - } - - - public static String getPageForSave(Article article, String title) { -// if ( TextUtils.isEmpty( title )){ -// title = getOptimizedNameForSave(article.getTitle()); -// } - String published = TimeUtil.stampToTime(article.getPublished() * 1000, "yyyy-MM-dd HH:mm"); - String canonical = article.getCanonical(); - String content = getFormatContentForSave(title, article.getContent()); - String author = getOptimizedAuthor(article.getOriginTitle(), article.getAuthor()); - - return "" + - "" + - "" + - "" + title + "" + - "" + - "
          " + - "
          " + - "

          " + title + "

          " + - "

          " + author + "

          " + - "

          " + published + "

          " + - "
          " + - "
          " + content + "
          " + - "
          " + - ""; - } - - - public static String getPageForDisplay(Article article) { - return getPageForDisplay(article, article.getContent(), Api.DISPLAY_RSS); - } - - public static String getPageForDisplay(Article article, String content, String referer) { - if (null == article) { - return ""; - } - // 获取排版文件路径(支持自定义的文件) - String typesettingCssPath = App.i().getExternalFilesDir(null) + File.separator + "config" + File.separator + "normalize.css"; - if (!new File(typesettingCssPath).exists()) { - typesettingCssPath = "file:///android_asset/css/normalize.css"; - } - - // 获取主题文件路径 - String themeCssPath; - if (WithPref.i().getThemeMode() == App.Theme_Day) { - themeCssPath = "file:///android_asset/css/article_theme_day.css"; - } else { - themeCssPath = "file:///android_asset/css/article_theme_night.css"; - } - - String author = getOptimizedAuthor(article.getOriginTitle(), article.getAuthor()); - - content = getFormatContentForDisplay(article.getId(), article.getCanonical(), content); - -// String videoJS = "", videoCSS = ""; -// if (!TextUtils.isEmpty(content) && content.indexOf(""; -// videoJS = ""; -// } - Feed feed = WithDB.i().getFeed(article.getOriginStreamId()); - String readabilityButton = ""; - String displayMode; - if (feed != null) { - displayMode = GlobalConfig.i().getDisplayMode(feed.getId()); - } else { - displayMode = Api.DISPLAY_RSS; - } - - if (Api.DISPLAY_RSS.equals(displayMode) || TextUtils.isEmpty(displayMode)) { - if (!Api.DISPLAY_READABILITY.equals(referer)) { - readabilityButton = "

          获取全文"; - } else { - readabilityButton = "

          恢复RSS内容"; - } - } - return "" + - "" + - "" + - "" + - "" + - "" + -// videoCSS + - "" + article.getTitle() + "" + - "" + - "
          " + - "
          " + - "

          " + article.getTitle() + "

          " + - "

          " + author + "

          " + - "

          " + TimeUtil.stampToTime(article.getPublished() * 1000, "yyyy-MM-dd HH:mm") + "

          " + - "
          " + - "
          " + - "
          " + content + - readabilityButton + - "
          " + - "
          " + - "" + // defer - "" + - "" + -// videoJS + - ""; - } - - - - /** - * 从 url 中获取文件名(含后缀) - * @param url 网址 - * @return 文件名(含后缀) - */ - public static String getFileNameExtByUrl(String url) { - if (TextUtils.isEmpty(url)) { - return ""; - } - String fileName; - int separatorIndex = url.lastIndexOf("/") + 1; - // 减少文件名太长的情况 - fileName = url.substring(separatorIndex, url.length()); - if (fileName.length() > 128) { - fileName = fileName.substring(0, 128); - } - - fileName = StringUtil.getOptimizedNameForSave(fileName); - return fileName; - } - - /** - * 处理文件名中的特殊字符和表情 - * - * @param fileName 文件名 - * @return 处理后的文件名 - */ - public static String getOptimizedNameForSave(String fileName) { - // 因为有些title会用 html中的转义。所以这里要改过来 - fileName = Html.fromHtml(fileName).toString(); - fileName = EmojiUtil.filterEmoji(fileName); - fileName = StringUtil.filterChar(fileName); - return fileName.trim(); - } - - - - private static String filterChar(String source) { - return source - .replace("\\", "") - .replace("/", "") - .replace(":", "") - .replace("*", "") - .replace("?", "") - .replace("\"", "") - .replace("<", "") - .replace(">", "") - .replace("|", "") - .replace("%", "_") -// .replace("#", "_") - .replace("&", "_") -// .replace("&", "_") - .replace("\n", "_"); - } - - - - /** - * 获取修整后的概要 - * - * @param tempHtml 原文 - * @return - */ - public static String getOptimizedSummary(String tempHtml) { - String result = ""; - if (TextUtils.isEmpty(tempHtml)) { - return result; - } - // 过滤其他标签 - tempHtml = delAllTag(tempHtml); - - // 过滤空格回车标签 - Pattern p_enter = Pattern.compile(REGEX_ENTER, Pattern.CASE_INSENSITIVE); - Matcher m_enter = p_enter.matcher(tempHtml); - tempHtml = m_enter.replaceAll(""); - - Pattern p_space = Pattern.compile(REGEX_SPACE, Pattern.CASE_INSENSITIVE); - Matcher m_space = p_space.matcher(tempHtml); - tempHtml = m_space.replaceAll(" "); - - tempHtml = tempHtml.trim(); - int showLength = tempHtml.length() < 90 ? tempHtml.length() : 90; - if (showLength > 0) { - result = tempHtml.substring(0, showLength); - } - return result; - } - - /** - * 获取修整后的文章,主要是过滤一些无用的标签 - * - * @param tempHtml 原文 - * @return - */ - public static String getOptimizedContent(String tempHtml) { - String result = ""; - if (TextUtils.isEmpty(tempHtml)) { - return result; - } - // 过滤广告 - tempHtml = delInoReaderAd(tempHtml); - // 过滤其他标签 - tempHtml = delScriptTag(tempHtml); - tempHtml = delStyleTag(tempHtml); - return tempHtml.trim(); - } - - // 过滤标签 - /** - * 定义script的正则表达式 - */ - private static final String REGEX_SCRIPT = "]*?>[\\s\\S]*?<\\/script>"; - /** - * 定义style的正则表达式 - */ - private static final String REGEX_STYLE = "]*?>[\\s\\S]*?<\\/style>"; - /** - * 定义HTML标签的正则表达式 - */ - private static final String REGEX_HTML = "<[^>]+>"; - /** - * 定义空格回车换行符 - */ - private static final String REGEX_SPACE = "\\s+"; - private static final String REGEX_ENTER = "\t|\r|\n"; - -// public static String delHtmlTag(String htmlStr) { -// // 过滤script标签 -// Pattern p_script = Pattern.compile(REGEX_SCRIPT, Pattern.CASE_INSENSITIVE); -// Matcher m_script = p_script.matcher(htmlStr); -// htmlStr = m_script.replaceAll(""); -// // 过滤style标签 -// Pattern p_style = Pattern.compile(REGEX_STYLE, Pattern.CASE_INSENSITIVE); -// Matcher m_style = p_style.matcher(htmlStr); -// htmlStr = m_style.replaceAll(""); -// // 过滤html标签 -// Pattern p_html = Pattern.compile(REGEX_HTML, Pattern.CASE_INSENSITIVE); -// Matcher m_html = p_html.matcher(htmlStr); -// htmlStr = m_html.replaceAll(""); -//// // 过滤空格回车标签 -//// Pattern p_space = Pattern.compile(REGEX_SPACE, Pattern.CASE_INSENSITIVE); -//// Matcher m_space = p_space.matcher(htmlStr); -//// htmlStr = m_space.replaceAll(""); -// return htmlStr.trim(); // 返回文本字符串 -// } - - public static String delScriptTag(String htmlStr) { - // 过滤script标签 - Pattern p_script = Pattern.compile(REGEX_SCRIPT, Pattern.CASE_INSENSITIVE); - Matcher m_script = p_script.matcher(htmlStr); - return m_script.replaceAll(""); - } - - public static String delStyleTag(String htmlStr) { - Pattern p_style = Pattern.compile(REGEX_STYLE, Pattern.CASE_INSENSITIVE); - Matcher m_style = p_style.matcher(htmlStr); - return m_style.replaceAll(""); - } - - public static String delAllTag(String htmlStr) { - // 过滤html标签 - Pattern p_html = Pattern.compile(REGEX_HTML, Pattern.CASE_INSENSITIVE); - Matcher m_html = p_html.matcher(htmlStr); - return m_html.replaceAll(""); - } - - /** - * 定义广告标签的正则表达式(这段正则压根不生效) - * // 过滤广告标签 - */ - private static final String REGEX_AD = "(?=\\
          )[\\s\\S]*?inoreader[\\s\\S]*?(?<=<\\/center>)"; - - private static String delInoReaderAd(String htmlStr) { - Pattern p_ad = Pattern.compile(REGEX_AD, Pattern.CASE_INSENSITIVE); - Matcher m_ad = p_ad.matcher(htmlStr); - return m_ad.replaceAll(""); - } - - -// /** -// * 从meta中获取页面编码 -// * -// * @param html -// * @return -// */ -// public static String getEncodingByMeta(String html) { -// String charset = null, temp = ""; -// List lines = new ArrayList<>(); -// try { -// BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(html.getBytes()))); -// while ((temp = in.readLine()) != null) { -// lines.add(temp); -// } -// -// for (String line : lines) { -// if (line.contains("http-equiv") && line.contains("charset")) { -//// KLog.e(line); -// String tmp = line.split(";")[1]; -// charset = tmp.substring(tmp.indexOf("=") + 1, tmp.indexOf("\"")); -// break; -// } -// } -// return charset; -// } catch (MalformedURLException e) { -// e.printStackTrace(); -// return charset; -// } catch (IOException e) { -// e.printStackTrace(); -// return charset; -// } -// } - -// /** -// * 获取字符串编码格式 -// * -// * @param str -// * @return -// */ -// public static String getEncode(String str) { -// final String[] encodes = new String[]{"UTF-8", "GBK", "GB2312", "ISO-8859-1", "ISO-8859-2"}; -// byte[] data = str.getBytes(); -// byte[] b = null; -// a: -// for (int i = 0; i < encodes.length; i++) { -// try { -// b = str.getBytes(encodes[i]); -// if (b.length != data.length) { -// continue;} -// for (int j = 0; j < b.length; j++) { -// if (b[j] != data[j]) { -// continue a; -// } -// } -// return encodes[i]; -// } catch (UnsupportedEncodingException e) { -// continue; -// } -// } -// return null; -// } - -// /** -// * 将字符串转换成指定编码格式 -// * -// * @param str -// * @param encode -// * @return -// */ -// public static String transcoding(String str, String encode) { -// String df = "ISO-8859-1"; -// try { -// String en = getEncode(str); -// if (en == null) { -// en = df;} -// return new String(str.getBytes(en), encode); -// } catch (UnsupportedEncodingException e) { -// return null; -// } -// } - - -// private static Set formats = new HashSet<>(); -// static { -// formats.add(".jpg"); -// formats.add(".jpeg"); -// formats.add(".png"); -// formats.add(".webp"); -// formats.add(".gif"); -// } -// /** -// * 根据url获取图片文件的后缀 -// * -// * @param url 网址 -// * @return 后缀名 -// */ -// public static String getImageSuffix(String url) { -// String suffix = url.substring(url.lastIndexOf("."), url.length()); -// if (!formats.contains(suffix.toLowerCase())) { -// return ".jpg"; -// } -// return suffix; -// } - - -// private static String getRandomString(int length) { -// String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; -// Random random = new Random(); -// StringBuilder sb = new StringBuilder(); -// for (int i = 0; i < length; i++) { -// int number = random.nextInt(62); -// sb.append(str.charAt(number)); -// } -// return sb.toString(); -// } - -} diff --git a/app/src/main/java/me/wizos/loread/utils/StringUtils.java b/app/src/main/java/me/wizos/loread/utils/StringUtils.java new file mode 100644 index 0000000..741eb02 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/StringUtils.java @@ -0,0 +1,415 @@ +package me.wizos.loread.utils; + +import android.text.TextUtils; +import android.util.Base64; + +import androidx.annotation.StringRes; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.wizos.loread.App; + + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class StringUtils { + private static final String TAG = "StringUtils"; + private final static HashMap ChnMap = getChnMap(); + + public static boolean isEmpty(List list) { + return list == null || list.isEmpty() || list.size() == 0; + } + public static boolean isEmpty(CharSequence str) { + return str == null || str.length() == 0; + } + +// public static String readable(String source, String pattern) { +// @SuppressLint("SimpleDateFormat") DateFormat format = new SimpleDateFormat(pattern); +// Calendar calendar = Calendar.getInstance(); +// try { +// Date date = format.parse(source); +// long curTime = calendar.getTimeInMillis(); +// calendar.setTime(date); +// //将MISC 转换成 sec +// long difSec = Math.abs((curTime - date.getTime()) / 1000); +// long difMin = difSec / 60; +// long difHour = difMin / 60; +// long difDate = difHour / 60; +// int oldHour = calendar.get(Calendar.HOUR); +// //如果没有时间 +// if (oldHour == 0) { +// //比日期:昨天今天和明天 +// if (difDate == 0) { +// return "今天"; +// } else if (difDate < DAY_OF_YESTERDAY) { +// return "昨天"; +// } else { +// @SuppressLint("SimpleDateFormat") DateFormat convertFormat = new SimpleDateFormat("yyyy-MM-dd"); +// return convertFormat.format(date); +// } +// } +// +// if (difSec < TIME_UNIT) { +// return difSec + "秒前"; +// } else if (difMin < TIME_UNIT) { +// return difMin + "分钟前"; +// } else if (difHour < HOUR_OF_DAY) { +// return difHour + "小时前"; +// } else if (difDate < DAY_OF_YESTERDAY) { +// return "昨天"; +// } else { +// @SuppressLint("SimpleDateFormat") DateFormat convertFormat = new SimpleDateFormat("yyyy-MM-dd"); +// return convertFormat.format(date); +// } +// } catch (ParseException e) { +// e.printStackTrace(); +// } +// return ""; +// } + + public static String toFirstCapital(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + public static String getString(@StringRes int id) { + return App.i().getResources().getString(id); + } + + public static String getString(@StringRes int id, Object... formatArgs) { + return App.i().getString(id, formatArgs); + } + + /** + * 只对url中的汉字部分进行 urlEncode 。 + * @param url + * @return + */ + public static String urlEncode(String url){ + if( TextUtils.isEmpty(url)){ + return null; + } + try { + StringBuffer sb = new StringBuffer(); + for (int i = 0, length = url.length(); i < length; i++) { + char c = url.charAt(i); + if (c <= '\u001f' || c >= '\u007f') { + sb.append( URLEncoder.encode( String.valueOf(c),"utf-8") ); + } else { + sb.append(c); + } + } + return sb.toString(); + }catch (UnsupportedEncodingException e){ + e.printStackTrace(); + return url; + } + } + + /** + * 将文本中的半角字符,转换成全角字符 + */ + public static String halfToFull(String input) { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) { + if (c[i] == 32) //半角空格 + { + c[i] = (char) 12288; + continue; + } + //根据实际情况,过滤不需要转换的符号 + //if (c[i] == 46) //半角点号,不转换 + // continue; + + if (c[i] > 32 && c[i] < 127) //其他符号都转换为全角 + c[i] = (char) (c[i] + 65248); + } + return new String(c); + } + + //功能:字符串全角转换为半角 + public static String fullToHalf(String input) { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) { + if (c[i] == 12288) //全角空格 + { + c[i] = (char) 32; + continue; + } + + if (c[i] > 65280 && c[i] < 65375) { + c[i] = (char) (c[i] - 65248); + } + + } + return new String(c); + } + + private static HashMap getChnMap() { + HashMap map = new HashMap<>(); + String cnStr = "零一二三四五六七八九十"; + char[] c = cnStr.toCharArray(); + for (int i = 0; i <= 10; i++) { + map.put(c[i], i); + } + cnStr = "〇壹贰叁肆伍陆柒捌玖拾"; + c = cnStr.toCharArray(); + for (int i = 0; i <= 10; i++) { + map.put(c[i], i); + } + map.put('两', 2); + map.put('百', 100); + map.put('佰', 100); + map.put('千', 1000); + map.put('仟', 1000); + map.put('万', 10000); + map.put('亿', 100000000); + return map; + } + + @SuppressWarnings("ConstantConditions") + public static int chineseNumToInt(String chNum) { + int result = 0; + int tmp = 0; + int billion = 0; + char[] cn = chNum.toCharArray(); + + // "一零二五" 形式 + if (cn.length > 1 && chNum.matches("^[〇零一二三四五六七八九壹贰叁肆伍陆柒捌玖]$")) { + for (int i = 0; i < cn.length; i++) { + cn[i] = (char) (48 + ChnMap.get(cn[i])); + } + return Integer.parseInt(new String(cn)); + } + + // "一千零二十五", "一千二" 形式 + try { + for (int i = 0; i < cn.length; i++) { + int tmpNum = ChnMap.get(cn[i]); + if (tmpNum == 100000000) { + result += tmp; + result *= tmpNum; + billion = billion * 100000000 + result; + result = 0; + tmp = 0; + } else if (tmpNum == 10000) { + result += tmp; + result *= tmpNum; + tmp = 0; + } else if (tmpNum >= 10) { + if (tmp == 0) + tmp = 1; + result += tmpNum * tmp; + tmp = 0; + } else { + if (i >= 2 && i == cn.length - 1 && ChnMap.get(cn[i - 1]) > 10) + tmp = tmpNum * ChnMap.get(cn[i - 1]) / 10; + else + tmp = tmp * 10 + tmpNum; + } + } + result += tmp + billion; + return result; + } catch (Exception e) { + return -1; + } + } + + public static int stringToInt(String str) { + if (str != null) { + String num = fullToHalf(str).replaceAll("\\s", ""); + try { + return Integer.parseInt(num); + } catch (Exception e) { + return chineseNumToInt(num); + } + } + return -1; + } + + public static String base64Decode(String str) { + byte[] bytes = Base64.decode(str, Base64.DEFAULT); + try { + return new String(bytes, StandardCharsets.UTF_8); + } catch (Exception e) { + return new String(bytes); + } + } + + public static String escape(String src) { + int i; + char j; + StringBuilder tmp = new StringBuilder(); + tmp.ensureCapacity(src.length() * 6); + for (i = 0; i < src.length(); i++) { + j = src.charAt(i); + if (Character.isDigit(j) || Character.isLowerCase(j) + || Character.isUpperCase(j)) + tmp.append(j); + else if (j < 256) { + tmp.append("%"); + if (j < 16) + tmp.append("0"); + tmp.append(Integer.toString(j, 16)); + } else { + tmp.append("%u"); + tmp.append(Integer.toString(j, 16)); + } + } + return tmp.toString(); + } + + public static boolean isJsonType(String str) { + boolean result = false; + if (!TextUtils.isEmpty(str)) { + str = str.trim(); + if (str.startsWith("{") && str.endsWith("}")) { + result = true; + } else if (str.startsWith("[") && str.endsWith("]")) { + result = true; + } + } + return result; + } + + public static boolean isJsonObject(String text) { + boolean result = false; + if (!TextUtils.isEmpty(text)) { + text = text.trim(); + if (text.startsWith("{") && text.endsWith("}")) { + result = true; + } + } + return result; + } + + public static boolean isJsonArray(String text) { + boolean result = false; + if (!TextUtils.isEmpty(text)) { + text = text.trim(); + if (text.startsWith("[") && text.endsWith("]")) { + result = true; + } + } + return result; + } + + public static boolean isTrimEmpty(String text) { + if (text == null) return true; + if (text.length() == 0) return true; + return text.trim().length() == 0; + } + + public static boolean startWithIgnoreCase(String src, String obj) { + if (src == null || obj == null) return false; + if (obj.length() > src.length()) return false; + return src.substring(0, obj.length()).equalsIgnoreCase(obj); + } + + public static boolean endWithIgnoreCase(String src, String obj) { + if (src == null || obj == null) return false; + if (obj.length() > src.length()) return false; + return src.substring(src.length() - obj.length()).equalsIgnoreCase(obj); + } + + /** + * delimiter 分隔符 + * elements 需要连接的字符数组 + */ + public static String join(CharSequence delimiter, CharSequence... elements) { + // 空指针判断 + Objects.requireNonNull(delimiter); + Objects.requireNonNull(elements); + + // Number of elements not likely worth Arrays.stream overhead. + // 此处用到了StringJoiner(JDK 8引入的类) + // 先构造一个以参数delimiter为分隔符的StringJoiner对象 + StringJoiner joiner = new StringJoiner(delimiter); + for (CharSequence cs : elements) { + // 拼接字符 + joiner.add(cs); + } + return joiner.toString(); + } + + public static String join(CharSequence delimiter, Iterable elements) { + if (elements == null) return null; + if (delimiter == null) delimiter = ","; + StringJoiner joiner = new StringJoiner(delimiter); + for (CharSequence cs : elements) { + joiner.add(cs); + } + return joiner.toString(); + } + + public static boolean isContainNumber(String company) { + Pattern p = Pattern.compile("[0-9]"); + Matcher m = p.matcher(company); + return m.find(); + } + + public static boolean isNumeric(String str) { + Pattern pattern = Pattern.compile("[0-9]*"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + public static String getBaseUrl(String url) { + if (url == null || !url.startsWith("http")) return null; + int index = url.indexOf("/", 9); + if (index == -1) { + return url; + } + return url.substring(0, index); + } + + // 移除字符串首尾空字符的高效方法(利用ASCII值判断,包括全角空格) + public static String trim(String s) { + if (isEmpty(s)) return ""; + int start = 0, len = s.length(); + int end = len - 1; + while ((start < end) && ((s.charAt(start) <= 0x20) || (s.charAt(start) == ' '))) { + ++start; + } + while ((start < end) && ((s.charAt(end) <= 0x20) || (s.charAt(end) == ' '))) { + --end; + } + if (end < len) ++end; + return ((start > 0) || (end < len)) ? s.substring(start, end) : s; + } + + public static String repeat(String str, int n) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < n; i++) { + stringBuilder.append(str); + } + return stringBuilder.toString(); + } + + public static String removeUTFCharacters(String data) { + if (data == null) return null; + Pattern p = Pattern.compile("\\\\u(\\p{XDigit}{4})"); + Matcher m = p.matcher(data); + StringBuffer buf = new StringBuffer(data.length()); + while (m.find()) { + String ch = String.valueOf((char) Integer.parseInt(m.group(1), 16)); + m.appendReplacement(buf, Matcher.quoteReplacement(ch)); + } + m.appendTail(buf); + return buf.toString(); + } + + public static String formatHtml(String html) { + if (isEmpty(html)) return ""; + return html.replaceAll("(?i)<(br[\\s/]*|/*p.*?|/*div.*?)>", "\n")// 替换特定标签为换行符 + .replaceAll("<[script>]*.*?>| ", "")// 删除script标签对和空格转义符 + .replaceAll("\\s*\\n+\\s*", "\n  ")// 移除空行,并增加段前缩进2个汉字 + .replaceAll("^[\\n\\s]+", "  ")//移除开头空行,并增加段前缩进2个汉字 + .replaceAll("[\\n\\s]+$", "");//移除尾部空行 + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/EmojiUtil.java b/app/src/main/java/me/wizos/loread/utils/SymbolUtil.java similarity index 83% rename from app/src/main/java/me/wizos/loread/utils/EmojiUtil.java rename to app/src/main/java/me/wizos/loread/utils/SymbolUtil.java index c1885f6..37fedad 100644 --- a/app/src/main/java/me/wizos/loread/utils/EmojiUtil.java +++ b/app/src/main/java/me/wizos/loread/utils/SymbolUtil.java @@ -9,7 +9,7 @@ * emoji regex */ -class EmojiUtil { +class SymbolUtil { private static final String MiscellaneousSymbolsAndPictographs = "[\\uD83C\\uDF00-\\uD83D\\uDDFF]"; private static final String SupplementalSymbolsAndPictographs = "[\\uD83E\\uDD00-\\uD83E\\uDDFF]"; @@ -96,4 +96,29 @@ static String filterEmoji(String source) { } + /** + * 过滤保存文件到存储器中会有问题的符号 + * @param str + * @return + */ + static String filterUnsavedSymbol(String str) { + return str + .replace("\\", "") + .replace("/", "") + .replace(":", "") + .replace("*", "") + .replace("?", "") + .replace("\"", "") + .replace("<", "") + .replace(">", "") + .replace("|", "") + .replace("%", "_") + .replace("#", "_") + .replace("&", "_") + .replace(" ", "_") + .replace("&", "_") + .replace("�", "_") + .replace("\r", "_") + .replace("\n", "_"); + } } diff --git a/app/src/main/java/me/wizos/loread/utils/TimeUtil.java b/app/src/main/java/me/wizos/loread/utils/TimeUtil.java index c17f2de..1a0fa9a 100644 --- a/app/src/main/java/me/wizos/loread/utils/TimeUtil.java +++ b/app/src/main/java/me/wizos/loread/utils/TimeUtil.java @@ -1,7 +1,6 @@ package me.wizos.loread.utils; -import com.socks.library.KLog; - +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -13,6 +12,167 @@ * Created by xdsjs on 2015/10/14. */ public class TimeUtil { +// public static String formatReadability(long timestamp) { +// // 如果给定的时间小于昨天凌晨,则直接用标准的 yyyy-MM-dd HH:mm 格式 +// } + + /** + * 时间差 + * + * @param date + * @return + */ + public static String getTimeFormatText(Date date) { + long minute = 60 * 1000;// 1分钟 + long hour = 60 * minute;// 1小时 + long day = 24 * hour;// 1天 + long month = 31 * day;// 月 + long year = 12 * month;// 年 + + if (date == null) { + return null; + } + long diff = new Date().getTime() - date.getTime(); + long r = 0; + if (diff > year) { + r = (diff / year); + return r + "年前"; + } + if (diff > month) { + r = (diff / month); + return r + "个月前"; + } + if (diff > day) { + r = (diff / day); + return r + "天前"; + } + if (diff > hour) { + r = (diff / hour); + return r + "小时前"; + } + if (diff > minute) { + r = (diff / minute); + return r + "分钟前"; + } + return "刚刚"; + } + + + /** * 用于显示时间 */ + public static final String TODAY = "今天"; + public static final String YESTERDAY = "昨天"; + + public static String getToday(String time) { + Calendar pre = Calendar.getInstance(); + Date predate = new Date(System.currentTimeMillis()); + pre.setTime(predate); + + Calendar cal = Calendar.getInstance(); + Date date = new Date(Long.parseLong(time) * 1000); + cal.setTime(date); + + if (cal.get(Calendar.YEAR) == (pre.get(Calendar.YEAR))) { + int diffDay = cal.get(Calendar.DAY_OF_YEAR) - pre.get(Calendar.DAY_OF_YEAR); + return showDateDetail(diffDay, time); + } + return time; + } + + /** * 将日期差显示为今天、明天或者星期 * @param diffDay * @param time * @return */ + private static String showDateDetail(int diffDay, String time){ + switch(diffDay){ + case -1: + return YESTERDAY; + case 0: + return TODAY; + default: + return getWeek(time); + } + } + /** * 计算周几 */ + public static String getWeek(String data) { + SimpleDateFormat sdr = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒"); + long lcc = Long.valueOf(data); + int i = Integer.parseInt(data); + String times = sdr.format(new Date(i * 1000L)); + Date date = null; + int mydate = 0; + String week = ""; + try { + date = sdr.parse(times); + Calendar cd = Calendar.getInstance(); + cd.setTime(date); + mydate = cd.get(Calendar.DAY_OF_WEEK); + // 获取指定日期转换成星期几 + } catch (ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (mydate == 1) { + week = "星期日"; + } else if (mydate == 2) { + week = "星期一"; + } else if (mydate == 3) { + week = "星期二"; + } else if (mydate == 4) { + week = "星期三"; + } else if (mydate == 5) { + week = "星期四"; + } else if (mydate == 6) { + week = "星期五"; + } else if (mydate == 7) { + week = "星期六"; + } + return week; + } + + + + + + /** + * 将 时间戳 转为指定的 格式 + * @param timestamp 时间戳(毫秒) + * @param pattern 要转为的格式(例如 yyyy-MM-dd HH:mm:ss) + * @return 格式化的时间 + */ + public static String format(long timestamp, String pattern) { + SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.getDefault()); + Date date = new Date(timestamp); + return dateFormat.format(date); + } + + public static int getCurrentHour() { + Calendar currentDate = new GregorianCalendar(Locale.CHINA); + return currentDate.get(Calendar.HOUR_OF_DAY); + } + + public static StringBuilder getTime(int time) { + if (time < 0) { + time = 0; + } + int cache = time / 1000; + int second = cache % 60; + cache = cache / 60; + int minute = cache % 60; + int hour = cache / 60; + StringBuilder timeStamp = new StringBuilder(); + if (hour > 0) { + timeStamp.append(hour); + timeStamp.append(":"); + } + if (minute < 10) { + timeStamp.append("0"); + } + timeStamp.append(minute); + timeStamp.append(":"); + + if (second < 10) { + timeStamp.append("0"); + } + timeStamp.append(second); + return timeStamp; + } /** * 获取当天的开始时间 @@ -63,92 +223,4 @@ public static long getFirstDayTimeOfYear() { currentDate.set(Calendar.DAY_OF_YEAR, 0); return currentDate.getTime().getTime(); } - - /** - * 获取当前时间,并转换为数据库次数表中需要的时间 - */ - public static int getCurrentTime() { - Calendar currentDate = new GregorianCalendar(); - int hour = currentDate.get(Calendar.HOUR_OF_DAY); - if (hour >= 2 && hour < 6) - return 0; - if (hour >= 6 && hour < 7) - return 1; - if (hour >= 7 && hour < 8) - return 2; - if (hour >= 8 && hour < 9) - return 3; - if (hour >= 9 && hour < 11) - return 4; - if (hour >= 11 && hour < 13) - return 5; - if (hour >= 13 && hour < 14) - return 6; - if (hour >= 14 && hour < 16) - return 7; - if (hour >= 16 && hour < 17) - return 8; - if (hour >= 17 && hour < 19) - return 9; - if (hour >= 19 && hour < 21) - return 10; - if (hour >= 21 && hour < 23) - return 11; - if (hour >= 23 && hour < 2) - return 12; - return 0; - } - - public static int getCurrentHour() { - Calendar currentDate = new GregorianCalendar(Locale.CHINA); - int hour = currentDate.get(Calendar.HOUR_OF_DAY); - return hour; - } - - /** - * 获取给定格式的当前时间 - * - * @param format 时间的格式 - * @return - */ - public static String getCurrentDate(String format) { - Date date = new Date(System.currentTimeMillis()); - SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.getDefault()); - return dateFormat.format(date); - } - - public static String getCurrentDateID(int position) { - Date dateID = new Date(System.currentTimeMillis() + position*24*3600*1000L); // 因为后面算的数目太大,超出其格式 int 的范围,所以加 L 使用 Long 类型 -// SimpleDateFormat dateYMD = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); -// SimpleDateFormat dateHMS = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); - SimpleDateFormat dateYMDHMS = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); - return dateYMDHMS.format(dateID); - } - - - /** - * 将时间戳(毫秒)转换为时间(yyyy-MM-dd HH:mm:ss) - */ - public static String stampToTime(long stamp, String pattern) { - SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.getDefault()); - Date date = new Date(stamp); -// Timestamp date = new Timestamp( stamp ); - return dateFormat.format(date); - } - - public static boolean compare(String HM1, String HM2) { - SimpleDateFormat timeHM = new SimpleDateFormat("HH:mm", Locale.getDefault()); - try { - Date date1 = timeHM.parse(HM1); - Date date2 = timeHM.parse(HM2); - KLog.e("时间为:" + HM1 + " " + HM2 + " " + (date1.getTime() > date2.getTime())); - return date1.getTime() > date2.getTime(); - } catch (Exception e) { - KLog.e("报错" + HM1 + " " + HM2); - KLog.e(e); - return false; - } - } - - } \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/utils/ToastUtil.java b/app/src/main/java/me/wizos/loread/utils/ToastUtil.java deleted file mode 100644 index a39fca6..0000000 --- a/app/src/main/java/me/wizos/loread/utils/ToastUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.wizos.loread.utils; - -import android.widget.Toast; - -import com.socks.library.KLog; - -import me.wizos.loread.App; - -/** - * Created by xdsjs on 2015/11/27. - */ -public class ToastUtil { - public static Toast toast; - public static void showLong(String msg) { - if (toast != null) { - toast.cancel(); - toast = null; - } - toast = Toast.makeText(App.i(), msg, Toast.LENGTH_LONG); - KLog.e(msg); - toast.show(); - } - public static void showShort(String msg) { - if (toast != null) { - toast.cancel(); - toast = null; - } - toast = Toast.makeText(App.i(), msg, Toast.LENGTH_SHORT); - KLog.e(msg); - toast.show(); - } -} diff --git a/app/src/main/java/me/wizos/loread/utils/Tool.java b/app/src/main/java/me/wizos/loread/utils/Tool.java index 947b6e6..c98c1d4 100644 --- a/app/src/main/java/me/wizos/loread/utils/Tool.java +++ b/app/src/main/java/me/wizos/loread/utils/Tool.java @@ -1,9 +1,12 @@ package me.wizos.loread.utils; import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; +import android.text.TextUtils; import android.view.View; +import com.hjq.toast.ToastUtils; import com.socks.library.KLog; import java.text.DecimalFormat; @@ -12,7 +15,6 @@ import me.wizos.loread.App; import me.wizos.loread.BuildConfig; import me.wizos.loread.R; -import me.wizos.loread.data.WithPref; /** * 一些比较杂的工具函数 @@ -24,7 +26,7 @@ public class Tool { public static void show(String msg) { if (BuildConfig.DEBUG) { KLog.e(msg); - ToastUtil.showLong(msg); + ToastUtils.show(msg); } } @@ -34,25 +36,35 @@ public static void printCallStatck() { } Throwable ex = new Throwable(); StackTraceElement[] stackElements = ex.getStackTrace(); - if (stackElements != null) { - KLog.e("-----------------------------------"); - for (StackTraceElement stackElement : stackElements) { - KLog.e(stackElement.getClassName() + "_" + stackElement.getFileName() + "_" + stackElement.getLineNumber() + "_" + stackElement.getMethodName()); - } - KLog.e("-----------------------------------"); + KLog.e("-----------------------------------"); + for (StackTraceElement stackElement : stackElements) { + KLog.e(stackElement.getClassName() + "_" + stackElement.getFileName() + "_" + stackElement.getLineNumber() + "_" + stackElement.getMethodName()); + } + KLog.e("-----------------------------------"); + } + + public static void printCallStatck2(Throwable ex) { + if (!BuildConfig.DEBUG) { + return; + } + StackTraceElement[] stackElements = ex.getStackTrace(); + KLog.e("-----------------------------------"); + for (StackTraceElement stackElement : stackElements) { + KLog.e(stackElement.getClassName() + "_" + stackElement.getFileName() + "_" + stackElement.getLineNumber() + "_" + stackElement.getMethodName()); } + KLog.e("-----------------------------------"); } public static void setBackgroundColor(View object) { - if (WithPref.i().getThemeMode() == App.Theme_Night) { - object.setBackgroundColor(App.i().getResources().getColor(R.color.article_dark_background)); + if (App.i().getUser().getThemeMode() == App.THEME_NIGHT) { + object.setBackgroundColor(App.i().getResources().getColor(R.color.dark_background)); } else { object.setBackgroundColor(App.i().getResources().getColor(R.color.white)); } } // public static void setWebViewsBGColor() { -// if (WithPref.i().getThemeMode() == App.Theme_Night) { +// if (WithPref.i().getThemeMode() == App.THEME_NIGHT) { // for (WebViewS webViewS : App.i().mWebViewCaches) { // webViewS.setBackgroundColor(App.i().getResources().getColor(R.color.article_dark_background)); // } @@ -66,9 +78,9 @@ public static void setBackgroundColor(View object) { public static String getNetFileSizeDescription(Context context, long size) { if (context != null && size == -1) { - return context.getString(R.string.unknow); + return context.getString(R.string.unknown); } - StringBuffer bytes = new StringBuffer(); + StringBuilder bytes = new StringBuilder(); DecimalFormat format = new DecimalFormat("###.0"); if (size >= 1024 * 1024 * 1024) { double i = (size / (1024.0 * 1024.0 * 1024.0)); @@ -79,7 +91,7 @@ public static String getNetFileSizeDescription(Context context, long size) { } else if (size >= 1024) { double i = (size / (1024.0)); bytes.append(format.format(i)).append("KB"); - } else if (size < 1024) { + } else { if (size <= 0) { bytes.append("0B"); } else { @@ -122,6 +134,46 @@ public static String getProcessName(Context context) { return null; } + /** + * 判断某个Activity 界面是否在前台 + * + * @param context + * @param className 某个界面名称 + * @return + */ + public static boolean isForeground(Context context, String className) { + if (context == null || TextUtils.isEmpty(className)) { + return false; + } + + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List list = am.getRunningTasks(1); + if (list != null && list.size() > 0) { + ComponentName cpn = list.get(0).topActivity; + if (className.equals(cpn.getClassName())) { + return true; + } + } + return false; + } + + public static boolean isAppForeground(Context context, String packageName) { + if (context == null || TextUtils.isEmpty(packageName)) { + return false; + } + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List appProcesses = am.getRunningAppProcesses(); + + if (appProcesses == null) + return false; + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return true; + } + } + return false; + } + // public boolean isDebug() { // try { // ApplicationInfo info = this.getApplicationInfo(); @@ -141,7 +193,7 @@ public static String getProcessName(Context context) { // } // ArrayList idListMD5 = new ArrayList<>(allArtsBeforeTime.size()); // for (Article article : allArtsBeforeTime) { -// idListMD5.add(StringUtil.str2MD5(article.getId())); +// idListMD5.add(StringUtil.MD5(article.getId())); // } // KLog.i("清除B:" + clearTime + "--" + allArtsBeforeTime.size() + "--" + days); // FileUtil.deleteHtmlDirList(idListMD5); @@ -174,10 +226,10 @@ public static String getProcessName(Context context) { // // String fileName; // for (File file:files){ -// if(!file.getName().endsWith(".html")){ +// if(!file.getUserName().endsWith(".html")){ // continue; // } -// fileName = file.getName().replace(".html",""); +// fileName = file.getUserName().replace(".html",""); // if(WithDB.i().isArticleExists(fileName)){ // continue; // } @@ -198,7 +250,7 @@ public static String getProcessName(Context context) { // List
          articles = WithDB.i().loadAllArts(); // for (Article article:articles){ // article.setReadState(Api.ART_UNREAD); -// if(!article.getSaveDir().equals(Api.SAVE_DIR_CACHE)){ +// if(!article.getSaveDir().equals(Api.NOT_FILED)){ // article.setReadState(Api.ART_UNREADING); // } // } diff --git a/app/src/main/java/me/wizos/loread/utils/UnreadCountUtil.java b/app/src/main/java/me/wizos/loread/utils/UnreadCountUtil.java deleted file mode 100644 index 927d94d..0000000 --- a/app/src/main/java/me/wizos/loread/utils/UnreadCountUtil.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.wizos.loread.utils; - -import android.text.TextUtils; - -import me.wizos.loread.data.WithDB; -import me.wizos.loread.net.Api; - -/** - * Created by Wizos on 2018/4/25. - */ - -public class UnreadCountUtil { - -// public static int getTagUnreadCount(String tagId) { -// int count = 0; -// if (TextUtils.isEmpty(tagId)) { -// return count; -// } -// if (tagId.contains(Api.U_READING_LIST)) { -// count = WithDB.i().getUnreadArtsCount(); -// } else if (tagId.contains(Api.U_NO_LABEL)) { -// count = WithDB.i().getUnreadArtsCountNoTag(); -// } else { -// if (App.unreadCountMap.containsKey(tagId)) { -// count = App.unreadCountMap.get(tagId); -//// KLog.e("【getGroupView】复用" + tagId ); -// } else { -// count = WithDB.i().getUnreadArtsCountByTag(tagId); -// App.unreadCountMap.put(tagId, count); -//// KLog.e("【getGroupView】初始" + tagId ); -// } -// } -// return count; -// } - - - public static int getTag(String id) { - int count = 0; - if (id.contains(Api.U_READING_LIST)) { - count = WithDB.i().getUnreadArtsCount(); - } else if (id.contains(Api.U_NO_LABEL)) { - count = WithDB.i().getUnreadArtsCountNoTag(); - } else { - try { - count = WithDB.i().getTag(id).getUnreadCount(); - } catch (Exception e) { - e.printStackTrace(); - } - } - return count; - } - - - public static int getUnreadCount(String id) { - if (TextUtils.isEmpty(id)) { - return 0; - } - if (id.startsWith("user/")) { - return getTag(id); - } else if (id.startsWith("feed/")) { - try { - return WithDB.i().getFeed(id).getUnreadCount(); - } catch (Exception e) { - e.printStackTrace(); - } - } - return 0; - } - - - -} diff --git a/app/src/main/java/me/wizos/loread/utils/UpdateUtil.java b/app/src/main/java/me/wizos/loread/utils/UpdateUtil.java deleted file mode 100644 index 78dca04..0000000 --- a/app/src/main/java/me/wizos/loread/utils/UpdateUtil.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.wizos.loread.utils; - -/** - * 一些升级用的业务数据 - * Created by Wizos on 2017/7/7. - */ - -public class UpdateUtil { - -} diff --git a/app/src/main/java/me/wizos/loread/utils/UriUtil.java b/app/src/main/java/me/wizos/loread/utils/UriUtil.java new file mode 100644 index 0000000..1c230db --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/UriUtil.java @@ -0,0 +1,108 @@ +package me.wizos.loread.utils; + +import android.net.Uri; +import android.text.TextUtils; +import android.webkit.URLUtil; + +import com.socks.library.KLog; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +public class UriUtil { + public static String getBaseUrl(String url){ + Uri uri = Uri.parse(url); + return uri.getScheme() + "://" + uri.getHost(); + } + + // https://www.hao123.com/favicon.ico + public static String getFaviconUrl(String url){ + Uri uri = Uri.parse(url); + return uri.getScheme() + "://" + uri.getHost() + "/favicon.ico"; + } + + public static String guessFileName(String url, String contentDisposition, String mimeType) { + String fileNameByGuess; + // 处理会把 epub 文件,识别为 bin 文件的 bug:https://blog.csdn.net/imesong/article/details/45568697 + if ("application/octet-stream".equals(mimeType)) { + if (TextUtils.isEmpty(contentDisposition)) { + // 从路径中获取 + fileNameByGuess = guessFileNameExt(url); + } else { + fileNameByGuess = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9); + if(fileNameByGuess.startsWith("\'") && fileNameByGuess.length()> 1){ + fileNameByGuess = fileNameByGuess.substring(1); + } + if(fileNameByGuess.endsWith("\'") && fileNameByGuess.length()> 1){ + fileNameByGuess = fileNameByGuess.substring(0,fileNameByGuess.length()-1); + } + if(fileNameByGuess.startsWith("\"") && fileNameByGuess.length()> 1){ + fileNameByGuess = fileNameByGuess.substring(1); + } + if(fileNameByGuess.endsWith("\"") && fileNameByGuess.length()> 1){ + fileNameByGuess = fileNameByGuess.substring(0,fileNameByGuess.length()-1); + } + } + }else { + fileNameByGuess = URLUtil.guessFileName(url, contentDisposition, mimeType); + } + + KLog.i("猜测的文件名为:" + mimeType + " -- " + fileNameByGuess + " -- " + contentDisposition ); + // 处理 url 中包含乱码中文的问题 + try { + fileNameByGuess = URLDecoder.decode(fileNameByGuess, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return fileNameByGuess; + } + + /** + * 从 url 中获取文件名(含后缀) + * + * @param url 网址 + * @return 文件名(含后缀) + */ + public static String guessFileNameExt(String url) { + if (TextUtils.isEmpty(url)) { + return ""; + } + String fileName,param = ""; + int end = url.lastIndexOf("?"); + if( end < 0 ){ + end = url.length(); + }else { + param = SymbolUtil.filterUnsavedSymbol(url.substring(end+1)); + if( !TextUtils.isEmpty(param) ){ + param = param + "_"; + } + } + int start = url.lastIndexOf("/",end); + fileName = param + url.substring(start + 1, end); + + // 减少文件名太长的情况 + if (fileName.length() > 64) { + fileName = fileName.substring(fileName.length() - 64); + } + + return FileUtil.getSaveableName(fileName); + } + + + public static String guessImageSuffix(String url) { + int typeIndex = url.lastIndexOf("."); + String fileExt = url.substring(typeIndex, url.length()); + if (fileExt.contains(".jpg")) { + url = url.substring(0, typeIndex) + ".jpg"; + } else if (fileExt.contains(".jpeg")) { + url = url.substring(0, typeIndex) + ".jpeg"; + } else if (fileExt.contains(".png")) { + url = url.substring(0, typeIndex) + ".png"; + } else if (fileExt.contains(".gif")) { + url = url.substring(0, typeIndex) + ".gif"; + } + KLog.d("【 修正后的url 】" + url); + return url; + } +} diff --git a/app/src/main/java/me/wizos/loread/utils/VideoInjectUtil.java b/app/src/main/java/me/wizos/loread/utils/VideoInjectUtil.java new file mode 100644 index 0000000..0a1af8a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/utils/VideoInjectUtil.java @@ -0,0 +1,32 @@ +package me.wizos.loread.utils; + +import me.wizos.loread.bridge.WebBridge; + +/** + * @author by Wizos on 2018/6/29. + */ + +public class VideoInjectUtil { + /** + * 注入全屏Js,对不同的视频网站分析相应的全屏控件——class标识 + * + * @param url 加载的网页地址 + * @return 注入的js内容,若不是需要适配的网址则返回空javascript + */ + public static String fullScreenJsFun(String url) { + String fullScreenFlags = null; + // http://v.qq.com/txp/iframe/player.html?vid=v0151eygqka、http://v.qq.com/iframe/player.html?vid=v0151eygqka + if (url.contains("qq.com/txp/iframe/player.html")) { + fullScreenFlags = "txp_btn_fullscreen"; + } else if (url.contains("sohu")) { + fullScreenFlags = "x-fs-btn"; + } else if (url.contains("letv")) { + fullScreenFlags = "hv_ico_screen"; + } + if (null != fullScreenFlags) { + return "javascript:document.getElementsByClassName('" + fullScreenFlags + "')[0].addEventListener('click',function(){" + WebBridge.TAG + ".toggleScreenOrientation();return false;});"; + } else { + return "javascript:"; + } + } +} diff --git a/app/src/main/java/me/wizos/loread/view/DragPhotoView.java b/app/src/main/java/me/wizos/loread/view/DragPhotoView.java deleted file mode 100644 index 3f6d066..0000000 --- a/app/src/main/java/me/wizos/loread/view/DragPhotoView.java +++ /dev/null @@ -1,325 +0,0 @@ -package me.wizos.loread.view; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import com.github.chrisbanes.photoview.PhotoView; - - -/** - * @author by wing on 2016/12/22. - */ - -public class DragPhotoView extends PhotoView { - private Paint mPaint; - - // downX - private float mDownX; - // down Y - private float mDownY; - - private float mTranslateY; - private float mTranslateX; - private float mScale = 1; - private int mWidth; - private int mHeight; - private float mMinScale = 0.5f; - private int mAlpha = 255; - private final static int MAX_TRANSLATE_Y = 500; - - private final static long DURATION = 300; - private boolean canFinish = false; - private boolean isAnimate = false; - - //is event on PhotoView - private boolean isTouchEvent = false; - private OnTapListener mTapListener; - private OnExitListener mExitListener; - private TapHelper tapHelper; - - public DragPhotoView(Context context) { - this(context, null); - } - - public DragPhotoView(Context context, AttributeSet attr) { - this(context, attr, 0); - } - - public DragPhotoView(Context context, AttributeSet attr, int defStyle) { - super(context, attr, defStyle); - mPaint = new Paint(); - mPaint.setColor(Color.BLACK); - tapHelper = new TapHelper(this, new TapHelper.OnTapListener() { - @Override - public void onLongTap(DragPhotoView view) { - if (mTapListener != null) { - mTapListener.onLongTap(view); - } - } -// @Override -// public void onTap(DragPhotoView view) { -// if(mTapListener!=null){ -// mTapListener.onTap(view); -// } -// } - }); - } - - @Override - protected void onDraw(Canvas canvas) { - mPaint.setAlpha(mAlpha); - canvas.drawRect(0, 0, mWidth, mHeight, mPaint); - canvas.translate(mTranslateX, mTranslateY); - canvas.scale(mScale, mScale, mWidth / 2, mHeight / 2); - super.onDraw(canvas); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mWidth = w; - mHeight = h; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - //only scale == 1 can drag - if (getScale() == 1) { - tapHelper.onTouch(event); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - onActionDown(event); - //change the canFinish flag - canFinish = !canFinish; - // 添加 -// tapHelper.sendLongClickMessage(event); - break; - // 这个是实现多点的关键,当屏幕检测到有多个手指同时按下之后,就触发了这个事件 - case MotionEvent.ACTION_POINTER_DOWN: -// tapHelper.removeLongClickMessage(); -// mState = STATE_MORE_FINGERS; - //消耗掉,不传递下去了 - return true; - case MotionEvent.ACTION_MOVE: -// tapHelper.move(event); - //in viewpager - if (mTranslateY == 0 && mTranslateX != 0) { - - //如果不消费事件,则不作操作 - if (!isTouchEvent) { - mScale = 1; - return super.dispatchTouchEvent(event); - } - } - - //single finger drag down - if (mTranslateY >= 0 && event.getPointerCount() == 1) { - onActionMove(event); - - //如果有上下位移 则不交给viewpager - if (mTranslateY != 0) { - isTouchEvent = true; - } - return true; - } - //防止下拉的时候双手缩放 - if (mTranslateY >= 0 && mScale < 0.95) { - return true; - } - break; - case MotionEvent.ACTION_UP: - //防止下拉的时候双手缩放 - if (event.getPointerCount() == 1) { - onActionUp(event); - isTouchEvent = false; - //judge finish or not -// postDelayed(new Runnable() { -// @Override -// public void run() { -// if (mTranslateX == 0 && mTranslateY == 0 && canFinish) { -// if (mTapListener != null) { -// mTapListener.onTap(DragPhotoView.this); -// } -// } -// canFinish = false; -// } -// }, 300); - } - break; - default: - break; - } - } - - return super.dispatchTouchEvent(event); - } - - private void onActionUp(MotionEvent event) { - - if (mTranslateY > MAX_TRANSLATE_Y) { - if (mExitListener != null) { - mExitListener.onExit(this, mTranslateX, mTranslateY, mWidth, mHeight); - } else { - throw new RuntimeException("DragPhotoView: onExitLister can't be null ! call setOnExitListener() "); - } - } else { - performAnimation(); - } - } - - private void onActionMove(MotionEvent event) { - float moveY = event.getY(); - float moveX = event.getX(); - mTranslateX = moveX - mDownX; - mTranslateY = moveY - mDownY; - - //保证上划到到顶还可以继续滑动 - if (mTranslateY < 0) { - mTranslateY = 0; - } - - float percent = mTranslateY / MAX_TRANSLATE_Y; - if (mScale >= mMinScale && mScale <= 1f) { - mScale = 1 - percent; - - mAlpha = (int) (255 * (1 - percent)); - if (mAlpha > 255) { - mAlpha = 255; - } else if (mAlpha < 0) { - mAlpha = 0; - } - } - if (mScale < mMinScale) { - mScale = mMinScale; - } else if (mScale > 1f) { - mScale = 1; - } - - invalidate(); - } - - private void performAnimation() { - getScaleAnimation().start(); - getTranslateXAnimation().start(); - getTranslateYAnimation().start(); - getAlphaAnimation().start(); - } - - private ValueAnimator getAlphaAnimation() { - final ValueAnimator animator = ValueAnimator.ofInt(mAlpha, 255); - animator.setDuration(DURATION); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mAlpha = (int) valueAnimator.getAnimatedValue(); - } - }); - - return animator; - } - - private ValueAnimator getTranslateYAnimation() { - final ValueAnimator animator = ValueAnimator.ofFloat(mTranslateY, 0); - animator.setDuration(DURATION); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mTranslateY = (float) valueAnimator.getAnimatedValue(); - } - }); - - return animator; - } - - private ValueAnimator getTranslateXAnimation() { - final ValueAnimator animator = ValueAnimator.ofFloat(mTranslateX, 0); - animator.setDuration(DURATION); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mTranslateX = (float) valueAnimator.getAnimatedValue(); - } - }); - - return animator; - } - - private ValueAnimator getScaleAnimation() { - final ValueAnimator animator = ValueAnimator.ofFloat(mScale, 1); - animator.setDuration(DURATION); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mScale = (float) valueAnimator.getAnimatedValue(); - invalidate(); - } - }); - - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - isAnimate = true; - } - - @Override - public void onAnimationEnd(Animator animator) { - isAnimate = false; - animator.removeAllListeners(); - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - }); - return animator; - } - - private void onActionDown(MotionEvent event) { - mDownX = event.getX(); - mDownY = event.getY(); - } - - public float getMinScale() { - return mMinScale; - } - - public void setMinScale(float minScale) { - mMinScale = minScale; - } - - public void setOnTapListener(OnTapListener listener) { - mTapListener = listener; - } - - public void setOnExitListener(OnExitListener listener) { - mExitListener = listener; - } - - public interface OnTapListener { - void onTap(DragPhotoView view); - - void onLongTap(DragPhotoView view); - } - - public interface OnExitListener { - void onExit(DragPhotoView view, float translateX, float translateY, float w, float h); - } - - public void finishAnimationCallBack() { - mTranslateX = -mWidth / 2 + mWidth * mScale / 2; - mTranslateY = -mHeight / 2 + mHeight * mScale / 2; - invalidate(); - } -} diff --git a/app/src/main/java/me/wizos/loread/view/ExpandableListViewS.java b/app/src/main/java/me/wizos/loread/view/ExpandableListViewS.java index 8bb1c26..8800e47 100644 --- a/app/src/main/java/me/wizos/loread/view/ExpandableListViewS.java +++ b/app/src/main/java/me/wizos/loread/view/ExpandableListViewS.java @@ -1,477 +1,402 @@ -package me.wizos.loread.view; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.ExpandableListAdapter; -import android.widget.ExpandableListView; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * @author Wizos on 2017/9/17. - */ - -public class ExpandableListViewS extends ExpandableListView implements AbsListView.OnScrollListener { // PinnedHeader - public ExpandableListViewS(Context context) { - super(context); - setOnScrollListener(this); - } - - public ExpandableListViewS(Context context, AttributeSet attrs) { - super(context, attrs); - setOnScrollListener(this); - } - - public ExpandableListViewS(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setOnScrollListener(this); - } - - - /** - * Adapter 接口 . 列表必须实现此接口 . - */ - private HeaderAdapter mAdapter; - public interface HeaderAdapter { - int PINNED_HEADER_GONE = 0; - int PINNED_HEADER_VISIBLE = 1; - int PINNED_HEADER_PUSHED_UP = 2; - /** - * 获取 Header 的状态 - * @return PINNED_HEADER_GONE, PINNED_HEADER_VISIBLE, PINNED_HEADER_PUSHED_UP 其中之一 - */ - int getHeaderState(int groupPosition, int childPosition); - - /** - * 配置 Header, 让 Header 知道显示的内容 - */ - void configureHeader(View header, int groupPosition, int childPosition, int alpha); - } - - - /** - * 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见 - */ - private View mHeaderView; - - /** - * 列表头是否可见 - */ - private boolean mHeaderViewVisible; - - private static final int MAX_ALPHA = 255; - private int mHeaderViewWidth; - private int mHeaderViewHeight; - - public void setPinnedHeaderView(View view) { - mHeaderView = view; - AbsListView.LayoutParams lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - view.setLayoutParams(lp); - - if (mHeaderView != null) { - setFadingEdgeLength(0); - } - requestLayout(); - } - - - private OnPinnedGroupClickListener mOnPinnedGroupClickListener; - public void setOnPinnedGroupClickListener(OnPinnedGroupClickListener onPinnedGroupClickListener) { - mOnPinnedGroupClickListener = onPinnedGroupClickListener; - } - public interface OnPinnedGroupClickListener { - void onHeaderClick(ExpandableListView parent, View v, int pinnedGroupPosition); - } - - private float mDownX; - private float mDownY; - - /** - * 如果 HeaderView 是可见的 , 此函数用于判断是否点击了 HeaderView, 并对做相应的处理 , 因为 HeaderView - * 是画上去的 , 所以设置事件监听是无效的 , 只有自行控制 . - */ - @Override - public boolean onTouchEvent(MotionEvent ev) { - - if (canScrollVertically(this)) { - getParent().requestDisallowInterceptTouchEvent(true); - } - if (mHeaderViewVisible) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mDownX = ev.getX(); - mDownY = ev.getY(); - if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) { - return true; - } - break; - case MotionEvent.ACTION_UP: - float x = ev.getX(); - float y = ev.getY(); - float offsetX = Math.abs(x - mDownX); - float offsetY = Math.abs(y - mDownY); - // 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发 headerClick() - if (x <= mHeaderViewWidth && y <= mHeaderViewHeight && offsetX <= mHeaderViewWidth - && offsetY <= mHeaderViewHeight) { - if (mHeaderView != null && mOnPinnedGroupClickListener != null) { - long packedPosition = getExpandableListPosition(this.getFirstVisiblePosition()); - int pinnedGroupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); - mOnPinnedGroupClickListener.onHeaderClick(this, mHeaderView, pinnedGroupPosition); - } - return true; - } - break; - default: - break; - } - } - return super.onTouchEvent(ev); - } - - @Override - public void setAdapter(ExpandableListAdapter adapter) { - super.setAdapter(adapter); - mAdapter = (HeaderAdapter) adapter; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mHeaderView != null) { - measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); - mHeaderViewWidth = mHeaderView.getMeasuredWidth(); - mHeaderViewHeight = mHeaderView.getMeasuredHeight(); - } - } - private int mOldState = -1; - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - final long flatPostion = getExpandableListPosition(getFirstVisiblePosition()); - final int groupPos = ExpandableListView.getPackedPositionGroup(flatPostion); - final int childPos = ExpandableListView.getPackedPositionChild(flatPostion); - int state = mAdapter.getHeaderState(groupPos, childPos); - if (mHeaderView != null && mAdapter != null && state != mOldState) { - mOldState = state; - mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); - } - configureHeaderView(groupPos, childPos); - } - - public void configureHeaderView(int groupPosition, int childPosition) { - if (mHeaderView == null || mAdapter == null || ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) { - return; - } - int state = mAdapter.getHeaderState(groupPosition, childPosition); - switch (state) { - case HeaderAdapter.PINNED_HEADER_GONE: { - mHeaderViewVisible = false; - break; - } - case HeaderAdapter.PINNED_HEADER_VISIBLE: { - mAdapter.configureHeader(mHeaderView, groupPosition, childPosition, MAX_ALPHA); - if (mHeaderView.getTop() != 0) { - mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); - } - mHeaderViewVisible = true; - break; - } - case HeaderAdapter.PINNED_HEADER_PUSHED_UP: { - View firstView = getChildAt(0); - int bottom = firstView.getBottom(); -// int itemHeight = firstView.getHeight(); - int headerHeight = mHeaderView.getHeight(); - int y; - int alpha; - if (bottom < headerHeight) { - y = (bottom - headerHeight); - alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; - } else { - y = 0; - alpha = MAX_ALPHA; - } - mAdapter.configureHeader(mHeaderView, groupPosition, childPosition, alpha); - if (mHeaderView.getTop() != y) { - mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); - } - mHeaderViewVisible = true; - break; - } - } - } - /** - * 列表界面更新时调用该方法(如滚动时) - */ - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (mHeaderViewVisible) { - // 分组栏是直接绘制到界面中,而不是加入到ViewGroup中 - drawChild(canvas, mHeaderView, getDrawingTime()); - } - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - final long flatPos = getExpandableListPosition(firstVisibleItem); - int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos); - int childPosition = ExpandableListView.getPackedPositionChild(flatPos); - configureHeaderView(groupPosition, childPosition); - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - - // 这段代码导致可以实现listview在悬停抽屉中滚动。但是又导致pinnedGroup失效 -// @Override -// protected void onAttachedToWindow() { -// super.onAttachedToWindow(); -// ViewParent parent = getParent(); -// while (parent != null) { -// setAssociatedListView(this); -// parent = parent.getParent(); +//package me.wizos.loreadx.view; +// +//import android.content.Context; +//import android.graphics.Canvas; +//import android.util.AttributeSet; +//import android.util.Log; +//import android.view.MotionEvent; +//import android.view.View; +//import android.view.ViewGroup; +//import android.widget.AbsListView; +//import android.widget.ExpandableListAdapter; +//import android.widget.ExpandableListView; +// +//import com.socks.library.KLog; +// +//import java.util.ArrayList; +//import java.util.Iterator; +//import java.util.List; +// +///** +// * @author Wizos on 2017/9/17. +// */ +// +//public class ExpandableListViewS extends ExpandableListView implements AbsListView.OnScrollListener { // PinnedHeader +// public ExpandableListViewS(Context context) { +// super(context); +// setOnScrollListener(this); +// } +// +// public ExpandableListViewS(Context context, AttributeSet attrs) { +// super(context, attrs); +// setOnScrollListener(this); +// } +// +// public ExpandableListViewS(Context context, AttributeSet attrs, int defStyleAttr) { +// super(context, attrs, defStyleAttr); +// setOnScrollListener(this); +// } +// +// +// /** +// * Adapter 接口 . 列表必须实现此接口 . +// */ +// private HeaderAdapter mAdapter; +// +// public interface HeaderAdapter { +// int PINNED_HEADER_GONE = 0; +// int PINNED_HEADER_VISIBLE = 1; +// int PINNED_HEADER_PUSHED_UP = 2; +// +// /** +// * 获取 Header 的状态 +// * +// * @return STICKY_HEADER_GONE, STICKY_HEADER_VISIBLE, STICKY_HEADER_PUSHED_UP 其中之一 +// */ +// int getHeaderState(int groupPosition, int childPosition); +// +// /** +// * 配置 Header, 让 Header 知道显示的内容 +// */ +// void configureHeader(View header, int groupPosition, int childPosition, int alpha); +// } +// +// +// /** +// * 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见 +// */ +// private View mHeaderView; +// +// /** +// * 列表头是否可见 +// */ +// private boolean mHeaderViewVisible; +// +// private static final int MAX_ALPHA = 255; +// private int mHeaderViewWidth; +// private int mHeaderViewHeight; +// +// public void setPinnedHeaderView(View view) { +// mHeaderView = view; +// AbsListView.LayoutParams lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); +// view.setLayoutParams(lp); +// +// if (mHeaderView != null) { +// setFadingEdgeLength(0); // } +// requestLayout(); // } -// @Override -// public boolean onInterceptTouchEvent(MotionEvent ev) { -// return true; +// +// +// private OnPinnedGroupClickListener mOnPinnedGroupClickListener; +// +// public void setOnPinnedGroupClickListener(OnPinnedGroupClickListener onPinnedGroupClickListener) { +// mOnPinnedGroupClickListener = onPinnedGroupClickListener; // } - public boolean canScrollVertically(AbsListView view) { - boolean canScroll = false; - - if (view != null && view.getChildCount() > 0) { - boolean isOnTop = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0; - boolean isAllItemsVisible = isOnTop && view.getLastVisiblePosition() == view.getChildCount(); - - if (isOnTop || isAllItemsVisible) { - canScroll = true; - } - } - - return canScroll; - } - - public void setAssociatedListView(AbsListView listView) { - listView.setOnScrollListener(associatedListViewListener); - updateListViewScrollState(listView); - } - - private final AbsListView.OnScrollListener associatedListViewListener = - new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - updateListViewScrollState(view); - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - updateListViewScrollState(view); - } - }; - - private void updateListViewScrollState(AbsListView listView) { - if (listView.getChildCount() == 0) { - isDraggable = true; - } else { - if (listView.getFirstVisiblePosition() == 0) { - View firstChild = listView.getChildAt(0); - if (firstChild.getTop() == listView.getPaddingTop()) { - isDraggable = true; - return; - } - } - isDraggable = false; - } - } - - private boolean isDraggable = true; - // 可拖动 - - - // 以上都是 设置 PinnedGroup 的内容 - - - /** - * 实现/接管 AbsListView.OnScrollListener 接口,让 setOnScrollListener 改造为 addOnScrollListener,可以添加更多的监听器。 - * 因为要实现“悬停抽屉只有在其内部的listview到达最顶部的时候,才能下拉”和“PinnedGroup”功能,都要添加 OnScrollListener 监听器。 - */ - private final CompositeScrollListener compositeScrollListener = new CompositeScrollListener(); - - // 其实再类内部{}只是代表在调用构造函数之前在{}中初始化,static{}只在类加载时调用 -// new子类的对象时,先调用父类staic{}里的东西,在调用子类里的static{},在调用父类{}的在调用父类构造方法,在调用子类构造方法 -// 调用子类或者父类的静态方法时,先调用父类的static{}在调用子类的static{} - { - super.setOnScrollListener(compositeScrollListener); - // 下面这段代码会影响 PinnedGroup 的功能 -// getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { -// @Override -// public void onGlobalLayout() { -// ViewGroup.LayoutParams leftLayoutParams = getLayoutParams(); -// ViewParent parent = getParent(); -// while (parent != null) { -// if (parent instanceof ScrollLayout) { -// int height = ((ScrollLayout) parent).getMeasuredHeight() - ((ScrollLayout) parent).minOffset; -// if (leftLayoutParams.height == height) { -// return; -// } else { -// leftLayoutParams.height = height; -// break; +// +// public interface OnPinnedGroupClickListener { +// void onHeaderClick(ExpandableListView parent, View v, int pinnedGroupPosition); +// } +// +// private float mDownX; +// private float mDownY; +// +// /** +// * 如果 HeaderView 是可见的 , 此函数用于判断是否点击了 HeaderView, 并对做相应的处理 , 因为 HeaderView +// * 是画上去的 , 所以设置事件监听是无效的 , 只有自行控制 . +// */ +// @Override +// public boolean onTouchEvent(MotionEvent ev) { +// +// if (canScrollVertically(this)) { +// getParent().requestDisallowInterceptTouchEvent(true); +// } +// if (mHeaderViewVisible) { +// switch (ev.getAction()) { +// case MotionEvent.ACTION_DOWN: +// mDownX = ev.getX(); +// mDownY = ev.getY(); +// if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) { +// return true; +// } +// break; +// case MotionEvent.ACTION_UP: +// float x = ev.getX(); +// float y = ev.getY(); +// float offsetX = Math.abs(x - mDownX); +// float offsetY = Math.abs(y - mDownY); +// +// // 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发 headerClick() +// if (x <= mHeaderViewWidth && y <= mHeaderViewHeight && offsetX <= mHeaderViewWidth +// && offsetY <= mHeaderViewHeight) { +// if (mHeaderView != null && mOnPinnedGroupClickListener != null) { +// long packedPosition = getExpandableListPosition(this.getFirstVisiblePosition()); +// int pinnedGroupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); +// mOnPinnedGroupClickListener.onHeaderClick(this, mHeaderView, pinnedGroupPosition); // } +// return true; // } -// parent = parent.getParent(); +// +// break; +// default: +// break; +// } +// } +// return super.onTouchEvent(ev); +// } +// +// @Override +// public void setAdapter(ExpandableListAdapter adapter) { +// super.setAdapter(adapter); +// mAdapter = (HeaderAdapter) adapter; +// } +// +// @Override +// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { +// super.onMeasure(widthMeasureSpec, heightMeasureSpec); +// if (mHeaderView != null) { +// measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); +// mHeaderViewWidth = mHeaderView.getMeasuredWidth(); +// mHeaderViewHeight = mHeaderView.getMeasuredHeight(); +// } +// } +// +// private int mOldState = -1; +// +// @Override +// protected void onLayout(boolean changed, int left, int top, int right, int bottom) { +// super.onLayout(changed, left, top, right, bottom); +// final long flatPostion = getExpandableListPosition(getFirstVisiblePosition()); +// final int groupPos = ExpandableListView.getPackedPositionGroup(flatPostion); +// final int childPos = ExpandableListView.getPackedPositionChild(flatPostion); +// int state = mAdapter.getHeaderState(groupPos, childPos); +// if (mHeaderView != null && mAdapter != null && state != mOldState) { +// mOldState = state; +// mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); +// } +// configureHeaderView(groupPos, childPos); +// } +// +// public void configureHeaderView(int groupPosition, int childPosition) { +// if (mHeaderView == null || mAdapter == null || ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) { +// return; +// } +// int state = mAdapter.getHeaderState(groupPosition, childPosition); +// switch (state) { +// case HeaderAdapter.PINNED_HEADER_GONE: { +// mHeaderViewVisible = false; +// Log.e("粘连布局:", "忽略: " + " [" + groupPosition + "," + childPosition + "] , " + mOldState + " , [" + mHeaderViewHeight + "," + mHeaderViewWidth + "]"); +// +// break; +// } +// case HeaderAdapter.PINNED_HEADER_VISIBLE: { +// mAdapter.configureHeader(mHeaderView, groupPosition, childPosition, MAX_ALPHA); +// Log.e("粘连布局:", "可见: " + " [" + groupPosition + "," + childPosition + "] , " + mOldState + " , [" + mHeaderViewHeight + "," + mHeaderViewWidth + "] " + MAX_ALPHA + " " + mHeaderView.getTop()); +// +// if (mHeaderView.getTop() != 0) { +// mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); // } -// setLayoutParams(leftLayoutParams); +// mHeaderViewVisible = true; +// break; // } -// }); - } - - /** - * 添加一个OnScrollListener,不会取代已添加OnScrollListener - *

          Make sure call this on UI thread

          - * - * @param listener the listener to add - */ - @Override - public void setOnScrollListener(final OnScrollListener listener) { - addOnScrollListener(listener); - } - - /** - * 添加一个OnScrollListener,不会取代已添加OnScrollListener - *

          Make sure call this on UI thread

          - * - * @param listener the listener to add - */ - public void addOnScrollListener(final OnScrollListener listener) { -// throwIfNotOnMainThread(); - compositeScrollListener.addOnScrollListener(listener); - } - - // +// case HeaderAdapter.PINNED_HEADER_PUSHED_UP: { +// View firstView = getChildAt(0); +// int bottom = firstView.getBottom(); +//// int itemHeight = firstView.getHeight(); +// int headerHeight = mHeaderView.getHeight(); +// int y; +// int alpha; +// if (bottom < headerHeight) { +// y = (bottom - headerHeight); +// alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; +// } else { +// y = 0; +// alpha = MAX_ALPHA; +// } +// mAdapter.configureHeader(mHeaderView, groupPosition, childPosition, alpha); +// if (mHeaderView.getTop() != y) { +// mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); +// } +// Log.e("粘连布局:", "推动: " + " [" + groupPosition + "," + childPosition + "] , " + mOldState + " , [" + mHeaderViewHeight + "," + mHeaderViewWidth + "] " + MAX_ALPHA + " " + mHeaderView.getTop()); +// +// mHeaderViewVisible = true; +// break; +// } +// } +// } +// // /** -// * 删除前一个添加scrollListener,只会删除完全相同的对象 -// *

          Make sure call this on UI thread.

          -// * -// * @param listener the listener to remove +// * 列表界面更新时调用该方法(如滚动时) // */ -// public void removeOnScrollListener(final OnScrollListener listener) { -//// throwIfNotOnMainThread(); -// compositeScrollListener.removeOnScrollListener(listener); +// @Override +// protected void dispatchDraw(Canvas canvas) { +// super.dispatchDraw(canvas); +// KLog.e("绘制到页面" + mHeaderViewVisible); +// if (mHeaderViewVisible) { +// // 分组栏是直接绘制到界面中,而不是加入到ViewGroup中 +// drawChild(canvas, mHeaderView, getDrawingTime()); +// } +// } +// +// @Override +// public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { +// final long flatPos = getExpandableListPosition(firstVisibleItem); +// int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos); +// int childPosition = ExpandableListView.getPackedPositionChild(flatPos); +// Log.e("展开", "滚动:" + firstVisibleItem + ", " + flatPos + ", " + groupPosition + ", " + childPosition); +// configureHeaderView(groupPosition, childPosition); // } - private class CompositeScrollListener implements OnScrollListener { - private final List scrollListenerList = new - ArrayList(); - - public void addOnScrollListener(OnScrollListener listener) { - if (listener == null) { - return; - } - for (OnScrollListener scrollListener : scrollListenerList) { - if (listener == scrollListener) { - return; - } - } - scrollListenerList.add(listener); - } - - public void removeOnScrollListener(OnScrollListener listener) { - if (listener == null) { - return; - } - Iterator iterator = scrollListenerList.iterator(); - while (iterator.hasNext()) { - OnScrollListener scrollListener = iterator.next(); - if (listener == scrollListener) { - iterator.remove(); - return; - } - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - List listeners = new ArrayList(scrollListenerList); - for (OnScrollListener listener : listeners) { - listener.onScrollStateChanged(view, scrollState); - } - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - List listeners = new ArrayList(scrollListenerList); - for (OnScrollListener listener : listeners) { - listener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); - } - } - } - - +// // @Override -// protected void onDetachedFromWindow() { -// super.onDetachedFromWindow(); +// public void onScrollStateChanged(AbsListView view, int scrollState) { // } - -// private void throwIfNotOnMainThread() { -// if (Looper.myLooper() != Looper.getMainLooper()) { -// throw new IllegalStateException("Must be invoked from the main thread."); +// +// public boolean canScrollVertically(AbsListView view) { +// boolean canScroll = false; +// +// if (view != null && view.getChildCount() > 0) { +// boolean isOnTop = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0; +// boolean isAllItemsVisible = isOnTop && view.getLastVisiblePosition() == view.getChildCount(); +// +// if (isOnTop || isAllItemsVisible) { +// canScroll = true; +// } +// } +// +// return canScroll; +// } +// +// public void setAssociatedListView(AbsListView listView) { +// listView.setOnScrollListener(associatedListViewListener); +// updateListViewScrollState(listView); +// } +// +// private final AbsListView.OnScrollListener associatedListViewListener = +// new AbsListView.OnScrollListener() { +// @Override +// public void onScrollStateChanged(AbsListView view, int scrollState) { +// updateListViewScrollState(view); +// } +// +// @Override +// public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { +// updateListViewScrollState(view); +// } +// }; +// +// private void updateListViewScrollState(AbsListView listView) { +// if (listView.getChildCount() == 0) { +// isDraggable = true; +// } else { +// if (listView.getFirstVisiblePosition() == 0) { +// View firstChild = listView.getChildAt(0); +// if (firstChild.getTop() == listView.getPaddingTop()) { +// isDraggable = true; +// return; +// } +// } +// isDraggable = false; // } // } - - - /** - * 阴影部分的代码 - */ -// private boolean showShadow = false; -// private View shadowView; +// +// private boolean isDraggable = true; +// // 可拖动 +// +// +// // 以上都是 设置 PinnedGroup 的内容 +// +// +// /** +// * 实现/接管 AbsListView.OnScrollListener 接口,让 setOnScrollListener 改造为 addOnScrollListener,可以添加更多的监听器。 +// * 因为要实现“悬停抽屉只有在其内部的listview到达最顶部的时候,才能下拉”和“PinnedGroup”功能,都要添加 OnScrollListener 监听器。 +// */ +// private final CompositeScrollListener compositeScrollListener = new CompositeScrollListener(); +// +// { +// // 其实再类内部{}只是代表在调用构造函数之前在{}中初始化,static{}只在类加载时调用 +// // new子类的对象时,先调用父类staic{}里的东西,在调用子类里的static{},在调用父类{}的在调用父类构造方法,在调用子类构造方法 +// // 调用子类或者父类的静态方法时,先调用父类的static{}在调用子类的static{} +// super.setOnScrollListener(compositeScrollListener); +// } +// // /** -// * 需要调用之前setOnScrollListener -// * @param shadowView the shadow view +// * 添加一个OnScrollListener,不会取代已添加OnScrollListener +// *

          Make sure call this on UI thread

          +// * +// * @param listener the listener to add // */ -// public void setTopShadowView(View shadowView) { -// if (shadowView == null) { -// return; +// @Override +// public void setOnScrollListener(final OnScrollListener listener) { +// addOnScrollListener(listener); +// } +// +// /** +// * 添加一个OnScrollListener,不会取代已添加OnScrollListener +// *

          Make sure call this on UI thread

          +// * +// * @param listener the listener to add +// */ +// public void addOnScrollListener(final OnScrollListener listener) { +//// throwIfNotOnMainThread(); +// compositeScrollListener.addOnScrollListener(listener); +// } +// +// // +//// /** +//// * 删除前一个添加scrollListener,只会删除完全相同的对象 +//// *

          Make sure call this on UI thread.

          +//// * +//// * @param listener the listener to remove +//// */ +//// public void removeOnScrollListener(final OnScrollListener listener) { +////// throwIfNotOnMainThread(); +//// compositeScrollListener.removeOnScrollListener(listener); +//// } +// private class CompositeScrollListener implements OnScrollListener { +// private final List scrollListenerList = new +// ArrayList(); +// +// public void addOnScrollListener(OnScrollListener listener) { +// if (listener == null) { +// return; +// } +// for (OnScrollListener scrollListener : scrollListenerList) { +// if (listener == scrollListener) { +// return; +// } +// } +// scrollListenerList.add(listener); // } -// this.shadowView = shadowView; -// addOnScrollListener(new OnScrollListener() { -// @Override -// public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { -// View firstChild = view.getChildAt(0); -// if (firstChild != null) { -// if (firstVisibleItem == 0 && firstChild.getTop() == 0) { -// showShadow = false; -// showTopShadow(); -// } else if (!showShadow) { -// showShadow = true; -// showTopShadow(); -// } +// +// public void removeOnScrollListener(OnScrollListener listener) { +// if (listener == null) { +// return; +// } +// Iterator iterator = scrollListenerList.iterator(); +// while (iterator.hasNext()) { +// OnScrollListener scrollListener = iterator.next(); +// if (listener == scrollListener) { +// iterator.remove(); +// return; // } // } +// } // -// @Override -// public void onScrollStateChanged(AbsListView view, int scrollState) { +// @Override +// public void onScrollStateChanged(AbsListView view, int scrollState) { +// List listeners = new ArrayList(scrollListenerList); +// for (OnScrollListener listener : listeners) { +// listener.onScrollStateChanged(view, scrollState); // } -// }); -// } -// private void showTopShadow() { -// if (shadowView == null || shadowView.getVisibility() == View.VISIBLE) { -// return; // } -// shadowView.setVisibility(View.VISIBLE); -// } -// private void hideTopShadow() { -// if (shadowView == null || shadowView.getVisibility() == View.GONE) { -// return; +// +// @Override +// public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { +// List listeners = new ArrayList(scrollListenerList); +// for (OnScrollListener listener : listeners) { +// listener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); +// } // } // } - -} - +//} +// diff --git a/app/src/main/java/me/wizos/loread/view/common/FriendlyCardView.java b/app/src/main/java/me/wizos/loread/view/FriendlyCardView.java similarity index 93% rename from app/src/main/java/me/wizos/loread/view/common/FriendlyCardView.java rename to app/src/main/java/me/wizos/loread/view/FriendlyCardView.java index 7371efb..eb1c6aa 100644 --- a/app/src/main/java/me/wizos/loread/view/common/FriendlyCardView.java +++ b/app/src/main/java/me/wizos/loread/view/FriendlyCardView.java @@ -3,13 +3,14 @@ * All Rights Reserved. */ -package me.wizos.loread.view.common; +package me.wizos.loread.view; import android.content.Context; import android.content.res.TypedArray; -import android.support.v7.widget.CardView; import android.util.AttributeSet; +import androidx.cardview.widget.CardView; + import me.wizos.loread.R; diff --git a/app/src/main/java/me/wizos/loread/view/IconFontView.java b/app/src/main/java/me/wizos/loread/view/IconFontView.java index d77202d..6930825 100644 --- a/app/src/main/java/me/wizos/loread/view/IconFontView.java +++ b/app/src/main/java/me/wizos/loread/view/IconFontView.java @@ -2,12 +2,12 @@ import android.content.Context; import android.graphics.Typeface; -import android.support.v7.widget.AppCompatTextView; import android.util.AttributeSet; +import androidx.appcompat.widget.AppCompatTextView; + /** - * * Created by Wizos on 2016/11/6. */ diff --git a/app/src/main/java/me/wizos/loread/view/ListView/ListViewS.java b/app/src/main/java/me/wizos/loread/view/ListView/ListViewS.java deleted file mode 100644 index 1c415f4..0000000 --- a/app/src/main/java/me/wizos/loread/view/ListView/ListViewS.java +++ /dev/null @@ -1,298 +0,0 @@ -package me.wizos.loread.view.ListView; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.AdapterView; - -import com.ditclear.swipelayout.SwipeDragLayout; -import com.socks.library.KLog; - -/** - * Created by Wizos on 2017/12/24. - */ - -public class ListViewS extends FastScrollListView implements Handler.Callback, SwipeDragLayout.SwipeListener { - - /* handler */ - private Handler mHandler; - - public ListViewS(Context context) { - this(context, null); - } - - public ListViewS(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ListViewS(Context context, AttributeSet attrs, int defStyleAttr) { - // 设置水波纹背景,无效 -// TypedValue typedValue = new TypedValue(); -// getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,typedValue, true); -// int[] attribute = new int[]{android.R.attr.selectableItemBackground}; -// TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(typedValue.resourceId, attribute); -// setBackground(typedArray.getDrawable(0)); -// typedArray.recycle(); - super(context, attrs, defStyleAttr); - mHandler = new Handler(this); - } - - - /** - * 自己写的长点击事件 - */ - /* Handler 的 Message 信息 */ - private static final int MSG_WHAT_LONG_CLICK = 1; - - /* onTouch里面的状态 */ - private static final int STATE_NOTHING = -1;//抬起状态 - private static final int STATE_DOWN = 0;//按下状态 - private static final int STATE_LONG_CLICK = 1;//长点击状态 - // private static final int STATE_SCROLL = 2;//SCROLL状态 - private static final int STATE_LONG_CLICK_FINISH = 3;//长点击已经触发完成 - // private static final int STATE_MORE_FINGERS = 4;//多个手指 - private int mState = STATE_NOTHING; - private OnListItemLongClickListener mOnListItemLongClickListener; - public void setOnListItemLongClickListener(OnListItemLongClickListener listener) { - mOnListItemLongClickListener = listener; - } - public interface OnListItemLongClickListener { - void onListItemLongClick(View view, int position); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_WHAT_LONG_CLICK: - //如果得到msg的时候state状态是Long Click的话 - if (mState == STATE_DOWN || mState == STATE_LONG_CLICK) { - //改为long click触发完成 - mState = STATE_LONG_CLICK_FINISH; - //得到长点击的位置 - int position = msg.arg1; - //找到那个位置的view - View view = getChildAt(position - getFirstVisiblePosition()); - //如果设置了监听器的话,就触发 - if (mOnListItemLongClickListener != null && position == pointToPosition(lastX, lastY)) { - mOnListItemLongClickListener.onListItemLongClick(view, position); - KLog.e("==" + msg.what); -// mVibrator.vibrate(100); // 触发震动 - } - } - break; - default: - break; - } - return true; - } - - /* 手指放下的坐标 */ - private int downX; - private int downY; - /* Handler 发送message需要延迟的时间 */ - private static final long CLICK_LONG_TRIGGER_TIME = 500;//1s - - /** - * remove message - */ - private void removeLongClickMessage() { - if (mHandler.hasMessages(MSG_WHAT_LONG_CLICK)) { - mHandler.removeMessages(MSG_WHAT_LONG_CLICK); - } - } - - /** - * send message - */ - private void sendLongClickMessage(int position) { - if (!mHandler.hasMessages(MSG_WHAT_LONG_CLICK)) { - Message message = new Message(); - message.what = MSG_WHAT_LONG_CLICK; - message.arg1 = position; - mHandler.sendMessageDelayed(message, CLICK_LONG_TRIGGER_TIME); - } - } - -// @Override -// public boolean onInterceptTouchEvent(MotionEvent ev) { -// switch (ev.getAction()) { -// case MotionEvent.ACTION_DOWN: -// break; -// case MotionEvent.ACTION_MOVE: -// // 容差值大概是24,再加上60 -// if (fingerLeftAndRightMove(ev)) { -// return false; -// } -// break; -// } -// return super.onInterceptTouchEvent(ev); -// } - - private int slideItemPosition = -1; - private int lastX; - private int lastY; - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - lastX = (int) ev.getX(); - lastY = (int) ev.getY(); - switch (ev.getAction()) { // & MotionEvent.ACTION_MASK - case MotionEvent.ACTION_DOWN: - //获取出坐标来 - downX = (int) ev.getX(); - downY = (int) ev.getY(); - //当前state状态为按下 - mState = STATE_DOWN; - sendLongClickMessage(pointToPosition(downX, downY)); // FIXME: 2016/5/4 【添加】修复 长按 bug - break; - // 这个是实现多点的关键,当屏幕检测到有多个手指同时按下之后,就触发了这个事件 - case MotionEvent.ACTION_POINTER_DOWN: - removeLongClickMessage(); -// mState = STATE_MORE_FINGERS; - //消耗掉,不传递下去了 - return true; - - case MotionEvent.ACTION_MOVE: - if (fingerNotMove(ev)) {//手指的范围在50以内 -// removeLongClickMessage(); -// return true; - } else if (fingerLeftAndRightMove(ev)) { - removeLongClickMessage(); - //将当前想要滑动哪一个传递给wrapperAdapter - int position = pointToPosition(downX, downY); - if (position != AdapterView.INVALID_POSITION) { - slideItemPosition = position; - } - } else { - removeLongClickMessage(); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - removeLongClickMessage(); - break; - default: - break; - } - return super.dispatchTouchEvent(ev); - } - -// -// /** -// * 是不是向右滑动 -// * -// * @return -// */ -// private boolean isFingerMoving2Right(MotionEvent ev) { -// return (ev.getX() - downX > mShortestDistance); -// } -// -// /** -// * 是不是向左滑动 -// * -// * @return -// */ -// private boolean isFingerMoving2Left(MotionEvent ev) { -// return (ev.getX() - downX < -mShortestDistance); -// } - -// private boolean fingerUpAndDownMove(MotionEvent ev) { -// return ((ev.getX() - downX < mShortestDistance && ev.getX() - downX > -mShortestDistance) && -// ev.getY() - downY > mShortestDistance || ev.getY() - downY < -mShortestDistance); -// } - - - /* 手指滑动的最短距离 */ - private int mShortestDistance = 25; - - /** - * 上下左右不能超出50 - * - * @param ev - * @return - */ - private boolean fingerNotMove(MotionEvent ev) { - return (downX - ev.getX() < mShortestDistance && downX - ev.getX() > -mShortestDistance && - downY - ev.getY() < mShortestDistance && downY - ev.getY() > -mShortestDistance); - } - - /** - * 左右得超出50,上下不能超出50 - * - * @param ev - * @return - */ - private boolean fingerLeftAndRightMove(MotionEvent ev) { - return ((ev.getX() - downX > mShortestDistance || ev.getX() - downX < -mShortestDistance) && - ev.getY() - downY < mShortestDistance && ev.getY() - downY > -mShortestDistance); - } - - /** - * 设置列表项左右滑动时的监听器 - * - * @param onItemSlideListener - */ - public void setItemSlideListener(OnItemSlideListener onItemSlideListener) { - mOnItemSlideListener = onItemSlideListener; - } - - private OnItemSlideListener mOnItemSlideListener; - - public interface OnItemSlideListener { - // int onSlideOpen(View view, int position, int direction); // FIXME: 2016/5/4 【实现划开自动返回】把返回类型由 void 改为 int - void onUpdate(View view, int position, float offset); - void onCloseLeft(View view, int position, int direction); - void onCloseRight(View view, int position, int direction); - void onClick(View view, int position); - void log(String layout); - } - - - @Override - public void onUpdate(View view, float offset) { - if (mOnItemSlideListener != null) { - mOnItemSlideListener.onUpdate(view, slideItemPosition, offset); - } - } - - @Override - public void onOpened(View view) { - - } - - @Override - public void onClosed(View view) { - - } - - @Override - public void onCloseLeft(View view) { - if (mOnItemSlideListener != null) { - mOnItemSlideListener.onCloseLeft(view, slideItemPosition, SwipeDragLayout.DIRECTION_LEFT); - } - } - - @Override - public void onCloseRight(View view) { - if (mOnItemSlideListener != null) { -// KLog.e("关闭右侧" + (lastX - downX) + "==" + (lastY - downY)); - mOnItemSlideListener.onCloseRight(view, slideItemPosition, SwipeDragLayout.DIRECTION_RIGHT); - } - } - - @Override - public void onClick(View view) { - if (mOnItemSlideListener != null) { - int position = pointToPosition(downX, downY); - mOnItemSlideListener.onClick(view, position); - } - } - - @Override - public void log(String layout) { - mOnItemSlideListener.log(layout); - } -} diff --git a/app/src/main/java/me/wizos/loread/view/SlideArrow/LeftSlideView.java b/app/src/main/java/me/wizos/loread/view/SlideArrow/LeftSlideView.java deleted file mode 100644 index 6bf6e1f..0000000 --- a/app/src/main/java/me/wizos/loread/view/SlideArrow/LeftSlideView.java +++ /dev/null @@ -1,103 +0,0 @@ -package me.wizos.loread.view.SlideArrow; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; - - -public class LeftSlideView extends View { - String TAG = "LeftSlideView"; - - Path path, arrowPath; - Paint paint, arrowPaint; - - //曲线的控制点 - float controlX = 0; - - String backViewColor = "#B3000000"; - - float backViewHeight = 0; - public static float width = 40; - public static float height = 260; - - public LeftSlideView(Context context) { - this(context, null); - } - - public LeftSlideView(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public LeftSlideView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - backViewHeight = dp2px(260); - - path = new Path(); - arrowPath = new Path(); - - paint = new Paint(); - paint.setAntiAlias(true); // 防止边缘的锯齿 - paint.setStyle(Paint.Style.FILL_AND_STROKE); // 既绘制轮廓也绘制内容 - paint.setColor(Color.parseColor(backViewColor)); - paint.setStrokeWidth(1); - - arrowPaint = new Paint(); - arrowPaint.setAntiAlias(true); - arrowPaint.setStyle(Paint.Style.FILL_AND_STROKE); - arrowPaint.setColor(Color.WHITE); - arrowPaint.setStrokeWidth(6); - - setAlpha(0); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - //画外面的东西 - path.reset(); - path.moveTo(0, 0); -// KLog.e("画笔X:" + backViewHeight + " " + controlX ); - path.quadTo(0, backViewHeight / 4, controlX / 3, backViewHeight * 3 / 8); -// KLog.e("画笔A:" + backViewHeight/4 + " " + controlX/3 + backViewHeight*3/8 ); - path.quadTo(controlX * 5 / 8, backViewHeight / 2, controlX / 3, backViewHeight * 5 / 8); -// KLog.e("画笔B:" + controlX*5/8 + " " + backViewHeight/2 + " " + controlX/3 + " " + backViewHeight*5/8 ); - path.quadTo(0, backViewHeight * 6 / 8, 0, backViewHeight); -// KLog.e("画笔C:" + backViewHeight*6/8 + " " + backViewHeight ); - canvas.drawPath(path, paint); - - //画里面的箭头 - arrowPath.reset(); - arrowPath.moveTo(controlX / 6 + (dp2px(5) * (controlX / (SlideArrowLayout.screenWidth / 6))), backViewHeight * 15 / 32); - arrowPath.lineTo(controlX / 6, backViewHeight * 16.1f / 32); - arrowPath.moveTo(controlX / 6, backViewHeight * 15.9f / 32); - arrowPath.lineTo(controlX / 6 + (dp2px(5) * (controlX / (SlideArrowLayout.screenWidth / 6))), backViewHeight * 17 / 32); - canvas.drawPath(arrowPath, arrowPaint); - - setAlpha(controlX / (SlideArrowLayout.screenWidth / 6)); - } - - public void cancelSlide() { -// KLog.e(TAG, "做 cancelSlide: " ); - updateControlPoint(0); - } - - public void updateControlPoint(float controlX) { -// KLog.e(TAG, "左 updateControlPoint: "+controlX); - this.controlX = controlX; - invalidate(); - } - - public float dp2px(final float dpValue) { - final float scale = getResources().getDisplayMetrics().density; - return dpValue * scale + 0.5f; - } -} diff --git a/app/src/main/java/me/wizos/loread/view/SlideArrow/RightSlideView.java b/app/src/main/java/me/wizos/loread/view/SlideArrow/RightSlideView.java deleted file mode 100644 index 4532b44..0000000 --- a/app/src/main/java/me/wizos/loread/view/SlideArrow/RightSlideView.java +++ /dev/null @@ -1,113 +0,0 @@ -package me.wizos.loread.view.SlideArrow; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; - - -public class RightSlideView extends View { - String TAG = "RightSlideView"; - - Path path, arrowPath; - Paint paint, arrowPaint; - - //曲线的控制点 - float controlX = 0; - - String backViewColor = "#B3000000"; - - float backViewHeight = 0; - public static float width = 40; - public static float height = 260; - - public RightSlideView(Context context) { - this(context, null); - } - - public RightSlideView(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public RightSlideView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - float rightSlideViewWidth = 0; - - private void init() { - rightSlideViewWidth = dp2px(40); - controlX = rightSlideViewWidth; - -// KLog.e("屏幕宽度A:" + rightSlideViewWidth ); - - backViewHeight = dp2px(260); - - path = new Path(); - arrowPath = new Path(); - - paint = new Paint(); - paint.setAntiAlias(true); - paint.setStyle(Paint.Style.FILL_AND_STROKE); - paint.setColor(Color.parseColor(backViewColor)); - //圆环宽度 - paint.setStrokeWidth(1); - - arrowPaint = new Paint(); - arrowPaint.setAntiAlias(true); - arrowPaint.setStyle(Paint.Style.FILL_AND_STROKE); - arrowPaint.setColor(Color.WHITE); - arrowPaint.setStrokeWidth(6); - - setAlpha(0); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); -// //画外面的东西 - path.reset(); -// KLog.e("画笔0:" + backViewHeight + " " + controlX ); - path.moveTo(rightSlideViewWidth, 0); - path.quadTo(rightSlideViewWidth, backViewHeight / 4, rightSlideViewWidth - controlX / 3, backViewHeight * 3 / 8); -// KLog.e("画笔1:" + rightSlideViewWidth + " " + backViewHeight/4 + " " + (rightSlideViewWidth - controlX/3) + " " + backViewHeight*3/8 ); - path.quadTo(rightSlideViewWidth - controlX * 5 / 8, backViewHeight / 2, rightSlideViewWidth - controlX / 3, backViewHeight * 5 / 8); -// KLog.e("画笔2:" + (rightSlideViewWidth - controlX*5/8) + " " + backViewHeight/2 + " " + (rightSlideViewWidth - controlX/3) + " " + backViewHeight*5/8 ); - path.quadTo(rightSlideViewWidth, backViewHeight * 6 / 8, rightSlideViewWidth, backViewHeight); -// KLog.e("画笔3:" + rightSlideViewWidth + " " + backViewHeight*6/8 + " " + rightSlideViewWidth + " " + backViewHeight); - canvas.drawPath(path, paint); - - // 画里面的箭头 - arrowPath.reset(); - arrowPath.moveTo(rightSlideViewWidth - controlX / 6, backViewHeight * 15 / 32); - arrowPath.lineTo(rightSlideViewWidth - controlX / 6 + (dp2px(5) * (controlX / (SlideArrowLayout.screenWidth / 6))), backViewHeight * 16.1f / 32); - arrowPath.moveTo(rightSlideViewWidth - controlX / 6 + (dp2px(5) * (controlX / (SlideArrowLayout.screenWidth / 6))), backViewHeight * 15.9f / 32); - arrowPath.lineTo(rightSlideViewWidth - controlX / 6, backViewHeight * 17 / 32); - canvas.drawPath(arrowPath, arrowPaint); - - setAlpha(controlX / (SlideArrowLayout.screenWidth / 6)); - } - - // 默认坐标为右边界,此处的由边界1080为x=0 - public void cancelSlide() { -// KLog.e(TAG, "右 cancelSlide: " ); - updateControlPoint(0); - } - - // 默认坐标为右边界,此处的由边界1080为x=0 - public void updateControlPoint(float controlX) { -// KLog.e(TAG, "右 updateControlPoint: "+ rightSlideViewWidth + " " + controlX); - this.controlX = controlX; - invalidate(); - } - - public float dp2px(final float dpValue) { - final float scale = getResources().getDisplayMetrics().density; - return dpValue * scale + 0.5f; - } -} diff --git a/app/src/main/java/me/wizos/loread/view/SlideArrow/SlideArrowLayout.java b/app/src/main/java/me/wizos/loread/view/SlideArrow/SlideArrowLayout.java deleted file mode 100644 index 893a3bb..0000000 --- a/app/src/main/java/me/wizos/loread/view/SlideArrow/SlideArrowLayout.java +++ /dev/null @@ -1,453 +0,0 @@ -package me.wizos.loread.view.SlideArrow; - -import android.app.Activity; -import android.content.Context; -import android.graphics.PixelFormat; -import android.os.Vibrator; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.WindowManager; -import android.widget.FrameLayout; - -import com.socks.library.KLog; - -import me.wizos.loread.R; - -import static android.content.Context.VIBRATOR_SERVICE; - -/** - * @author Wizos on 2018/8/22. - */ - -public class SlideArrowLayout extends FrameLayout { - String TAG = "SwipeArrowLayout"; - - public SlideArrowLayout(Context context) { - this(context, null); - } - - public SlideArrowLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SlideArrowLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public void setActivity(Activity activity) { - this.activity = activity; - onCreate(); - } - - boolean isLeftEage = false;//判断是否从左边缘划过来 - boolean isRightEage = false;//判断是否从左边缘划过来 - - Activity activity; - float shouldFinishPix = 0; // 结束取消拉伸 - public static float screenWidth = 0; - float screenHeight = 0; - int leftSlidingTriggerValue = 0; - int rightSlidingTriggerValue = 0; - - // 滑动 触控区域 - int CANSLIDE_LENGTH = 56;//16 - - View backView; - View rightView; - LeftSlideView leftSlideView; - RightSlideView rightSlideView; - WindowManager windowManager; - WindowManager.LayoutParams leftLayoutParams; - WindowManager.LayoutParams rightLayoutParams; - - float x; - float y; - float downX; - - protected void onCreate() { - WindowManager manager = activity.getWindowManager(); - DisplayMetrics outMetrics = new DisplayMetrics(); - manager.getDefaultDisplay().getMetrics(outMetrics); - screenWidth = outMetrics.widthPixels; - screenHeight = outMetrics.heightPixels; - shouldFinishPix = screenWidth / 3; - - //添加返回的View - windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE); - leftLayoutParams = new WindowManager.LayoutParams(); - leftLayoutParams.width = dp2px(LeftSlideView.width); - leftLayoutParams.height = 0; - leftLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; //设置window type,type是关键,这里的"2002" 表示系统级窗口,你也可以试试2003 - leftLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - leftLayoutParams.format = PixelFormat.RGBA_8888; // 设置图片格式,1 效果为背景透明 - leftLayoutParams.x = (int) (-screenWidth / 2); - - rightLayoutParams = new WindowManager.LayoutParams(); - rightLayoutParams.width = dp2px(RightSlideView.width); - rightLayoutParams.height = 0; - rightLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; - rightLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - rightLayoutParams.format = PixelFormat.RGBA_8888; - rightLayoutParams.x = (int) (screenWidth + screenWidth / 2); - - - backView = LayoutInflater.from(activity).inflate(R.layout.left_slide_view, null); - leftSlideView = backView.findViewById(R.id.slideBackView); - - - rightView = LayoutInflater.from(activity).inflate(R.layout.right_slide_view, null); - rightSlideView = rightView.findViewById(R.id.rightSlideView); - - leftSlidingTriggerValue = dp2px(CANSLIDE_LENGTH); - rightSlidingTriggerValue = getResources().getDisplayMetrics().widthPixels - dp2px(CANSLIDE_LENGTH); - - mTouchSlop = ViewConfiguration.get(activity).getScaledPagingTouchSlop(); - } - - - @Override - public void addView(View view) { - super.addView(view, 0); - } - -// /** -// * 作用是把触摸事件的分发方法,其返回值代表触摸事件是否被当前 View 处理完成(true/false)。 -// * @param ev -// * @return -// */ -// @Override -// public boolean dispatchTouchEvent(MotionEvent ev) { -//// KLog.e("分配触摸事件:" + ev.getAction()); -// x = ev.getRawX(); -// y = ev.getRawY(); -// switch (ev.getAction()){ -// case MotionEvent.ACTION_DOWN: -//// downX = ev.getRawX(); -// KLog.e("按下位置:" + x + " " + dp2px(CANSLIDE_LENGTH) ); -// if(x<=leftSlidingTriggerValue && slideListener.canSlideRight() ){ -// isLeftEage = true; -// return onTouchEvent(ev); -// }else if( x>=rightSlidingTriggerValue && slideListener.canSlideLeft() ){ -// isRightEage = true; -// return onTouchEvent(ev); -// } -// break; -// -//// case MotionEvent.ACTION_MOVE: -//// float moveX = x - downX; -//// if(isLeftEage){ -////// if(Math.abs(moveX)<=shouldFinishPix){ -////// slideBackView.updateControlPoint(Math.abs(moveX)/2); -////// } -////// leftLayoutParams.y = (int) (ev.getRawY()-screenHeight/2); -////// windowManager.updateViewLayout(backView, leftLayoutParams); -//// } -//// break; -// default: -// break; -// } -// return super.dispatchTouchEvent(ev); -// } - - - private int mTouchSlop; - private float mLastMotionX; - private float mLastMotionY; - private float mInitialMotionX; - private float mInitialMotionY; - private int mActivePointerId = -1; - private boolean mIsBeingDragged; - private boolean mIsUnableToDrag; -// @Override -// public boolean onInterceptTouchEvent(MotionEvent ev) { -// /* -// * This method JUST determines whether we want to intercept the motion. -// * If we return true, onMotionEvent will be called and we do the actual scrolling there. -// * *这种方法只是确定我们是否要拦截运动。 -// * *如果返回true,将调用onTouchEvent,并在那里进行实际滚动。 -// */ -// -// final int action = ev.getAction() & MotionEvent.ACTION_MASK; -// -// // Always take care of the touch gesture being complete. -// // 始终注意触摸手势是否完整。 -// if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { -// // Release the drag. -// KLog.e(TAG, "拦截完成"); -//// resetTouch(); -// return false; -// } -// -// // Nothing more to do here if we have decided whether or not we are dragging. -// // 如果我们已经决定是否要拖下去,这里就没什么可做的了。 -// if (action != MotionEvent.ACTION_DOWN) { -// if (mIsBeingDragged) { -// KLog.v(TAG, "Intercept returning true!"); -// return true; -// } -// if (mIsUnableToDrag) { -// KLog.v(TAG, "Intercept returning false!"); -// return false; -// } -// } -// -// switch (action) { -// case MotionEvent.ACTION_MOVE: { -// /* -// * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check -// * whether the user has moved far enough from his original down touch. -// * mIsBeingDragged == false,否则捷径会抓住它。检查用户是否已经离开他最初的向下触摸足够远。 -// */ -// -// /* -// * Locally do absolute value. mLastMotionY is set to the y value of the down event. -// * 本地做绝对值。运动设置为向下事件的y值。 -// */ -// final int activePointerId = mActivePointerId; -// if (activePointerId == -1) { -// // If we don't have a valid id, the touch down wasn't on content. -// // 如果我们没有有效的id,那么点击就不在内容上了。 -// break; -// } -// -// final int pointerIndex = ev.findPointerIndex(activePointerId); -// final float x = ev.getX(pointerIndex); -// final float dx = x - mLastMotionX; -// final float xDiff = Math.abs(dx); -// final float y = ev.getY(pointerIndex); -// final float yDiff = Math.abs(y - mInitialMotionY); -// KLog.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); -// -// //isGutterDrag是判断是否在两个页面之间的缝隙内移动 -// if (dx != 0 && !isGutterDrag(mLastMotionX, dx) -// && canScroll(this, false, (int) dx, (int) x, (int) y)) { -// // Nested view has scrollable area under this point. Let it be handled there. -// mLastMotionX = x; -// mLastMotionY = y; -// mIsUnableToDrag = true; -// return false; -// } -// if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { -// if (DEBUG) Log.v(TAG, "Starting drag!"); -// mIsBeingDragged = true; -// requestParentDisallowInterceptTouchEvent(true); -// setScrollState(SCROLL_STATE_DRAGGING); -// mLastMotionX = dx > 0 -// ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; -// mLastMotionY = y; -// setScrollingCacheEnabled(true); -// } else if (yDiff > mTouchSlop) { -// // The finger has moved enough in the vertical -// // direction to be counted as a drag... abort -// // any attempt to drag horizontally, to work correctly -// // with children that have scrolling containers. -// if (DEBUG) Log.v(TAG, "Starting unable to drag!"); -// mIsUnableToDrag = true; -// } -// if (mIsBeingDragged) { -// // Scroll to follow the motion event -// if (performDrag(x)) { -// ViewCompat.postInvalidateOnAnimation(this); -// } -// } -// break; -// } -// -// case MotionEvent.ACTION_DOWN: { -// /* -// * Remember location of down touch. -// * ACTION_DOWN always refers to pointer index 0. -// */ -// mLastMotionX = mInitialMotionX = ev.getX(); -// mLastMotionY = mInitialMotionY = ev.getY(); -// mActivePointerId = ev.getPointerId(0); -// mIsUnableToDrag = false; -// -// mIsScrollStarted = true; -// mScroller.computeScrollOffset(); -// if (mScrollState == SCROLL_STATE_SETTLING -// && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { -// // Let the user 'catch' the pager as it animates. -// mScroller.abortAnimation(); -// mPopulatePending = false; -// populate(); -// mIsBeingDragged = true; -// requestParentDisallowInterceptTouchEvent(true); -// setScrollState(SCROLL_STATE_DRAGGING); -// } else { -// completeScroll(false); -// mIsBeingDragged = false; -// } -// -// if (DEBUG) { -// Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY -// + " mIsBeingDragged=" + mIsBeingDragged -// + "mIsUnableToDrag=" + mIsUnableToDrag); -// } -// break; -// } -// -// case MotionEvent.ACTION_POINTER_UP: -// onSecondaryPointerUp(ev); -// break; -// default: -// break; -// } -// -// if (mVelocityTracker == null) { -// mVelocityTracker = VelocityTracker.obtain(); -// } -// mVelocityTracker.addMovement(ev); -// -// /* -// * The only time we want to intercept motion events is if we are in the -// * drag mode. -// */ -// return mIsBeingDragged; -// } - - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { -// KLog.e("拦截事件"); - // 如果我们已经决定是否要拖下去,这里就没什么可做的了。 - if (ev.getAction() != MotionEvent.ACTION_DOWN) { - if (isLeftEage || isRightEage) { - KLog.v(TAG, "Intercept returning true!"); - return true; - } - } - x = ev.getRawX(); - y = ev.getRawY(); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - downX = ev.getRawX(); - break; - case MotionEvent.ACTION_MOVE: -// KLog.e("移动位置:" + x + " " + dp2px(CANSLIDE_LENGTH)); - if (downX <= leftSlidingTriggerValue && (ev.getRawX() - downX) > mTouchSlop && slideListener.canSlideRight()) { - isLeftEage = true; - leftLayoutParams.height = dp2px(LeftSlideView.height); - leftLayoutParams.y = (int) (ev.getRawY() - screenHeight / 2); - windowManager.addView(backView, leftLayoutParams); - return true; - } else if (downX >= rightSlidingTriggerValue && (downX - ev.getRawX()) > mTouchSlop && slideListener.canSlideLeft()) { - isRightEage = true; - rightLayoutParams.height = dp2px(RightSlideView.height); - rightLayoutParams.y = (int) (ev.getRawY() - screenHeight / 2); - windowManager.addView(rightView, rightLayoutParams); - return true; - } - break; - default: - break; - } - - return onInterceptHoverEvent(ev); - } - /** - * - * 1、无论是对于 View 还是 ViewGroup来说,一个 触摸事件(MotionEvent 对象) 只要能传递给这个 View/ViewGroup , - * 那么这个 View/ViewGroup 的 dispatchTouchEvent(MotionEvent event) 就一定会被调用 - - 2、如果一个 View/ViewGroup 的 onTouchEvent(MotionEvent event) 方法被调用, - 那么其返回值代表这个 View/ViewGroup 有没有成功的处理这个事件, - 如果返回的 true,那么这个触摸事件接下来的一系列(直到手指松开之前) 都会传递给这个 View/ViewGroup 处理, - 但是这个过程中其父 ViewGroup 仍然可以通过 interceptTouchEvent(MotionEvent e) 方法拦截这个触摸事件, - 如果在传递的过程中被拦截了,那么久不会传递到这个 View/ViewGroup 上。 - - 3、无论何时,只要一个 View/ViewGroup 的 onTouchEvent(MotionEvent event) 方法返回了 false, - 证明这个 View/ViewGroup 没有处理完成这个触摸事件, - 那么接下来的一系列的触摸事件都不会传递给当前 View/ViewGroup 处理。 - */ - - - /** - * 这个是 ViewGroup 控件处理触摸事件的方法,一般来说,ViewGroup 控件的触摸事件在这个方法中处理。 - * 如果这个方法返回 true,证明当前触摸事件被当前 ViewGroup 控件处理完成并消耗了。 - * 如果返回 false,证明当前触摸事件没有被当前 ViewGroup 控件处理完成。 - * - * @param ev - * @return - */ - private float lastMoveX; - - @Override - public boolean onTouchEvent(MotionEvent ev) { -// KLog.e("事件传递到了 左右箭头 的 onTouchEvent,响应触摸事件:" + ev.getAction()); - x = ev.getRawX(); - y = ev.getRawY(); - switch (ev.getAction()) { - case MotionEvent.ACTION_MOVE: - float moveX = x - downX; - if (isLeftEage) { - if (Math.abs(moveX) <= shouldFinishPix) { - leftSlideView.updateControlPoint(Math.abs(moveX) / 2); - } - leftLayoutParams.y = (int) (ev.getRawY() - screenHeight / 2); - windowManager.updateViewLayout(backView, leftLayoutParams); - } else if (isRightEage) { - if (Math.abs(moveX) <= shouldFinishPix) { - rightSlideView.updateControlPoint(Math.abs(moveX) / 2); - } - rightLayoutParams.y = (int) (ev.getRawY() - screenHeight / 2); - windowManager.updateViewLayout(rightView, rightLayoutParams); - } - if ((lastMoveX < shouldFinishPix && Math.abs(moveX) >= shouldFinishPix)) { - Vibrator vibrator = (Vibrator) activity.getSystemService(VIBRATOR_SERVICE); - vibrator.vibrate(300); - } - lastMoveX = Math.abs(moveX); - break; - case MotionEvent.ACTION_UP: - //从左边缘划过来,并且最后在屏幕的三分之一外 - if (isLeftEage) { - if (x >= shouldFinishPix) { - slideListener.slideRightSuccess(); - } - windowManager.removeViewImmediate(backView); - } else if (isRightEage) { - if (x <= screenWidth - shouldFinishPix) { - slideListener.slideLeftSuccess(); - } - windowManager.removeViewImmediate(rightView); - } - isLeftEage = false; - isRightEage = false; - leftSlideView.cancelSlide(); - rightSlideView.cancelSlide(); - break; - default: - break; - } - return true; - } - - SlideListener slideListener; - - public void setSlideListener(SlideListener slideListener) { - this.slideListener = slideListener; - } - - public interface SlideListener { - boolean canSlideLeft(); - - void slideLeftSuccess(); - - boolean canSlideRight(); - - void slideRightSuccess(); - } - - - public int dp2px(final float dpValue) { - final float scale = getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); - } - - -} diff --git a/app/src/main/java/me/wizos/loread/view/SwipeRefreshLayoutS.java b/app/src/main/java/me/wizos/loread/view/SwipeRefreshLayoutS.java index 0243273..1fe52bb 100644 --- a/app/src/main/java/me/wizos/loread/view/SwipeRefreshLayoutS.java +++ b/app/src/main/java/me/wizos/loread/view/SwipeRefreshLayoutS.java @@ -1,13 +1,14 @@ package me.wizos.loread.view; import android.content.Context; -import android.support.v4.widget.SwipeRefreshLayout; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.AbsListView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + /** * 下拉刷新控件的包装。解决下拉和左右滑动冲突的问题 * Created by Wizos on 2016/3/30. @@ -35,31 +36,29 @@ public void setViewGroup(View view) { } /** - * * 当SwipeRefreshLayout 不只有 listView一个子view时,向下滑动的时候就会出现还没有滑倒listview顶部就触发下拉刷新的动作。 * 看SwipeRefreshLayout源码可以看到在onInterceptTouchEvent里面有这样的一段代码 - if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) { - // Fail fast if we're not in a state where a swipe is possible - return false; - } - - 其中有个canChildScrollUp方法,在往下看 - public boolean canChildScrollUp() { - if (android.os.Build.VERSION.SDK_INT < 14) { - if (mTarget instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTarget; - return absListView.getChildCount() > 0 - && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) - .getTop() < absListView.getPaddingTop()); - } else { - return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; - } - } else { - return ViewCompat.canScrollVertically(mTarget, -1); - } - } - 决定子view 能否滑动就是在这里了,所以我们只有写一个类继承SwipeRefreshLayout,然后重写该方法即可 - * + * if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) { + * // Fail fast if we're not in a state where a swipe is possible + * return false; + * } + *

          + * 其中有个canChildScrollUp方法,在往下看 + * public boolean canChildScrollUp() { + * if (android.os.Build.VERSION.SDK_INT < 14) { + * if (mTarget instanceof AbsListView) { + * final AbsListView absListView = (AbsListView) mTarget; + * return absListView.getChildCount() > 0 + * && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) + * .getTop() < absListView.getPaddingTop()); + * } else { + * return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; + * } + * } else { + * return ViewCompat.canScrollVertically(mTarget, -1); + * } + * } + * 决定子view 能否滑动就是在这里了,所以我们只有写一个类继承SwipeRefreshLayout,然后重写该方法即可 */ @Override public boolean canChildScrollUp() { @@ -73,7 +72,6 @@ public boolean canChildScrollUp() { } - /** * 作者:秋天的雨滴 * 链接:https://www.jianshu.com/p/04d799608c2e @@ -103,24 +101,6 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); } - -// /* 手指放下的坐标 */ -// private float mXDown; -// private float mYDown; -// /* 手指滑动的最短距离 */ -// private float mShortestDistance = 25f; -// -// /** -// * 手指左右移动:左右得超出50,上下不能超出50 -// * -// * @param ev -// * @return -// */ -// private boolean fingerLeftAndRightMove(MotionEvent ev) { -// return ((ev.getX() - mXDown > mShortestDistance || ev.getX() - mXDown < -mShortestDistance) && -// ev.getY() - mYDown < mShortestDistance && ev.getY() - mYDown > -mShortestDistance); -// } - @Override public boolean onTouchEvent(MotionEvent ev) { // KLog.e("事件传递到了 下拉控件的 onTouchEvent"); diff --git a/app/src/main/java/me/wizos/loread/view/TapHelper.java b/app/src/main/java/me/wizos/loread/view/TapHelper.java deleted file mode 100644 index bd38362..0000000 --- a/app/src/main/java/me/wizos/loread/view/TapHelper.java +++ /dev/null @@ -1,114 +0,0 @@ -package me.wizos.loread.view; - -import android.os.Handler; -import android.os.Message; -import android.view.MotionEvent; -import android.view.View; - - -/** - * Created by Wizos on 2018/6/10. - */ - -public class TapHelper implements Handler.Callback { - private Handler mHandler; - private static final int MSG_WHAT_LONG_CLICK = 1; - /* Handler 发送message需要延迟的时间 */ - private static final long CLICK_LONG_TRIGGER_TIME = 500;//1s - private OnTapListener tapListener; - private View view; - private Message message; - - TapHelper(View view, OnTapListener tapListener) { - this.view = view; - this.tapListener = tapListener; - mHandler = new Handler(this); - } - - - public void sendLongClickMessage() { - if (!mHandler.hasMessages(MSG_WHAT_LONG_CLICK)) { - message = Message.obtain(); - message.what = MSG_WHAT_LONG_CLICK; - mHandler.sendMessageDelayed(message, CLICK_LONG_TRIGGER_TIME); - } - } - - public void removeLongClickMessage() { - if (mHandler.hasMessages(MSG_WHAT_LONG_CLICK)) { - mHandler.removeMessages(MSG_WHAT_LONG_CLICK); - } - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_WHAT_LONG_CLICK: - //如果得到msg的时候state状态是Long Click的话 - //如果设置了监听器的话,就触发 - if (tapListener != null && isNotMove() && lastTime > downTime) { - tapListener.onLongTap((DragPhotoView) view); - } - break; - default: - break; - } - return true; - } - - - private int lastX; - private int lastY; - private int downX; - private int downY; - private long downTime = 0; - private long lastTime = 0; - /* 手指滑动的最短距离 */ - private int mShortestDistance = 25; - - /** - * 上下左右不能超出50 - * - * @return - */ - private boolean isNotMove() { - return (downX - lastX < mShortestDistance && downX - lastX > -mShortestDistance && - downY - lastY < mShortestDistance && downY - lastY > -mShortestDistance); - } - - public void onTouch(MotionEvent event) { -// KLog.e("触发:" + event.getAction() ); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - downX = (int) event.getX(); - downY = (int) event.getY(); - lastTime = 0; - downTime = System.currentTimeMillis(); - removeLongClickMessage(); - sendLongClickMessage(); - break; - // 这个是实现多点的关键,当屏幕检测到有多个手指同时按下之后,就触发了这个事件 - case MotionEvent.ACTION_POINTER_DOWN: - removeLongClickMessage(); - break; - case MotionEvent.ACTION_MOVE: - lastX = (int) event.getX(); - lastY = (int) event.getY(); - lastTime = System.currentTimeMillis(); - break; - case MotionEvent.ACTION_UP: - removeLongClickMessage(); - break; - case MotionEvent.ACTION_CANCEL: - removeLongClickMessage(); - break; - default: - break; - } - } - - public interface OnTapListener { - void onLongTap(DragPhotoView view); - } - -} diff --git a/app/src/main/java/me/wizos/loread/view/WebViewS.java b/app/src/main/java/me/wizos/loread/view/WebViewS.java index 2f54102..d2fe19e 100644 --- a/app/src/main/java/me/wizos/loread/view/WebViewS.java +++ b/app/src/main/java/me/wizos/loread/view/WebViewS.java @@ -6,13 +6,10 @@ import android.view.ViewGroup; import android.webkit.CookieManager; import android.webkit.WebSettings; -import android.webkit.WebView; import com.socks.library.KLog; import me.wizos.loread.App; -import me.wizos.loread.BuildConfig; -import me.wizos.loread.utils.Tool; import me.wizos.loread.view.webview.NestedScrollWebView; /** @@ -23,63 +20,113 @@ public class WebViewS extends NestedScrollWebView { private boolean isReadability = false; +// private int downX,downY; @SuppressLint("NewApi") - public WebViewS(Context context) { + public WebViewS(final Context context) { // 传入 application activity 来防止 activity 引用被滥用。 // 创建 WebView 传的是 Application , Application 本身是无法弹 Dialog 的 。 所以只能无反应 ! // 这个问题解决方案只要你创建 WebView 时候传入 Activity , 或者 自己实现 onJsAlert 方法即可。 - super(context); initSettingsForWebPage(); - + // 获取手指点击事件的xy坐标 +// setOnTouchListener( new View.OnTouchListener() { +// @Override +// public boolean onTouch(View arg0, MotionEvent arg1) { +// downX = (int) arg1.getX(); +// downY = (int) arg1.getY(); +// return false; +// } +// }); // 作者:Wing_Li // 链接:https://www.jianshu.com/p/3fcf8ba18d7f - setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (!BuildConfig.DEBUG) { - return false; - } - HitTestResult result = ((WebViewS) v).getHitTestResult(); - if (null == result) { - return false; - } - int type = result.getType(); - if (type == WebView.HitTestResult.UNKNOWN_TYPE) { - return false; - } - - // 这里可以拦截很多类型,我们只处理图片类型就可以了 - switch (type) { - case WebView.HitTestResult.PHONE_TYPE: // 处理拨号 - Tool.show("长按手机号"); - break; - case WebView.HitTestResult.EMAIL_TYPE: // 处理Email - Tool.show("长按邮件"); - break; - case WebView.HitTestResult.GEO_TYPE: // 地图类型 - Tool.show("长按地图"); - break; - case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超链接 - Tool.show("长按超链接:" + result.getExtra()); - break; - case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: // 一个SRC_IMAGE_ANCHOR_TYPE类型表明了一个拥有图片为子对象的超链接。 - Tool.show("长按图片:" + result.getExtra()); - break; - case WebView.HitTestResult.IMAGE_TYPE: // 处理长按图片的菜单项 - // 获取图片的路径 - String saveImgUrl = result.getExtra(); - Tool.show("长按图片:" + saveImgUrl); - // 跳转到图片详情页,显示图片 - break; - default: - break; - } - return true; - } - }); - +// setOnLongClickListener(new View.OnLongClickListener() { +// @Override +// public boolean onLongClick(View v) { +// if (!BuildConfig.DEBUG) { +// return false; +// } +// final HitTestResult status = ((WebViewS) v).getHitTestResult(); +// if (null == status) { +// return false; +// } +// int type = status.getType(); +// if (type == WebView.HitTestResult.UNKNOWN_TYPE) { +// return false; +// } +// +// // 这里可以拦截很多类型,我们只处理图片类型就可以了 +// switch (type) { +// case WebView.HitTestResult.PHONE_TYPE: // 处理拨号 +// Tool.show("长按手机号"); +// break; +// case WebView.HitTestResult.EMAIL_TYPE: // 处理Email +// Tool.show("长按邮件"); +// break; +// case WebView.HitTestResult.GEO_TYPE: // 地图类型 +// Tool.show("长按地图"); +// break; +// case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超链接 +// final LongClickPopWindow webViewLongClickedPopWindow = new LongClickPopWindow(context,WebView.HitTestResult.SRC_ANCHOR_TYPE, ScreenUtil.dp2px(context,120), ScreenUtil.dp2px(context,90)); +// webViewLongClickedPopWindow.showAtLocation(v, Gravity.TOP|Gravity.LEFT, downX, downY + 10); +// +// webViewLongClickedPopWindow.getView(R.id.webview_copy_link) +// .setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// webViewLongClickedPopWindow.dismiss(); +// //获取剪贴板管理器: +// ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); +// // 创建普通字符型ClipData +// ClipData mClipData = ClipData.newPlainText("url", status.getExtra()); +// // 将ClipData内容放到系统剪贴板里。 +// cm.setPrimaryClip(mClipData); +// ToastUtil.showLong("复制成功"); +// } +// }); +// webViewLongClickedPopWindow.getView(R.id.webview_share_link) +// .setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// webViewLongClickedPopWindow.dismiss(); +// Intent sendIntent = new Intent(Intent.ACTION_SEND); +// sendIntent.setType("text/plain"); +// sendIntent.putExtra(Intent.EXTRA_TEXT, status.getExtra()); +// sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +// context.startActivity(Intent.createChooser(sendIntent, "分享到")); +// } +// }); +// +// +//// SnackbarUtil.Long(WebViewS.this.getRootView(), "链接:" + status.getExtra()) +//// .setAction("复制", new View.OnClickListener() { +//// @Override +//// public void onClick(View v) { +//// //获取剪贴板管理器: +//// ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); +//// // 创建普通字符型ClipData +//// ClipData mClipData = ClipData.newPlainText("url", status.getExtra()); +//// // 将ClipData内容放到系统剪贴板里。 +//// cm.setPrimaryClip(mClipData); +//// ToastUtil.showLong("复制成功"); +//// } +//// }).show(); +// break; +// case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: // 一个SRC_IMAGE_ANCHOR_TYPE类型表明了一个拥有图片为子对象的超链接。 +// Tool.show("长按图片:" + status.getExtra()); +// break; +// case WebView.HitTestResult.IMAGE_TYPE: // 处理长按图片的菜单项 +// // 获取图片的路径 +// String saveImgUrl = status.getExtra(); +// Tool.show("长按图片:" + saveImgUrl); +// // 跳转到图片详情页,显示图片 +// break; +// default: +// break; +// } +// return true; +// } +// }); } @@ -99,9 +146,14 @@ private void initSettingsForWebPage() { setScrollbarFadingEnabled(false); // 先设置背景色为tranaparent 透明色 - this.setBackgroundColor(0); + setBackgroundColor(0); WebSettings webSettings = this.getSettings(); - webSettings.setJavaScriptEnabled(true); + + webSettings.setTextZoom(100); + // 设置最小的字号,默认为8 + webSettings.setMinimumFontSize(10); + // 设置最小的本地字号,默认为8 + webSettings.setMinimumLogicalFontSize(10); // 设置自适应屏幕,两者合用 // 设置使用 宽 的 Viewpoint,默认是false @@ -111,12 +163,8 @@ private void initSettingsForWebPage() { webSettings.setUseWideViewPort(true); // 缩放至屏幕的大小:如果webview内容宽度大于显示区域的宽度,那么将内容缩小,以适应显示区域的宽度, 默认是false webSettings.setLoadWithOverviewMode(true); - - webSettings.setTextZoom(100); - // 设置最小的字号,默认为8 - webSettings.setMinimumFontSize(10); - // 设置最小的本地字号,默认为8 - webSettings.setMinimumLogicalFontSize(10); + // NARROW_COLUMNS 适应内容大小 , SINGLE_COLUMN 自适应屏幕 + webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); //缩放操作 webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。 @@ -125,56 +173,53 @@ private void initSettingsForWebPage() { webSettings.setDefaultTextEncodingName("UTF-8"); - // NARROW_COLUMNS 适应内容大小 , SINGLE_COLUMN 自适应屏幕 - webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); -// webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); + webSettings.setJavaScriptEnabled(true); // 支持通过js打开新的窗口 webSettings.setJavaScriptCanOpenWindowsAutomatically(false); /* 缓存 */ webSettings.setDomStorageEnabled(true); // 临时简单的缓存(必须保留,否则无法播放优酷视频网页,其他的可以) webSettings.setAppCacheEnabled(true); // 支持H5的 application cache 的功能 - // webSettings.setDatabaseEnabled(true); // 支持javascript读写db - - // 允许在Android 5.0上 Webview 加载 Http 与 Https 混合内容。作者:Wing_Li,链接:https://www.jianshu.com/p/3fcf8ba18d7f - webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + webSettings.setDatabaseEnabled(true); // 支持javascript读写db + /* 根据cache-control获取数据 */ +// webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); + webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); - /* 新增 */ // 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭 webSettings.setAllowFileAccessFromFileURLs(false); // 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源 webSettings.setAllowUniversalAccessFromFileURLs(false); // 允许访问文件 webSettings.setAllowFileAccess(true); - // 保存密码数据 webSettings.setSavePassword(true); // 保存表单数据 webSettings.setSaveFormData(true); - //根据cache-control获取数据。 - webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); - - webSettings.setMediaPlaybackRequiresUserGesture(true); - CookieManager instance = CookieManager.getInstance(); instance.setAcceptCookie(true); instance.setAcceptThirdPartyCookies(this, true); + // 允许在Android 5.0上 Webview 加载 Http 与 Https 混合内容。作者:Wing_Li,链接:https://www.jianshu.com/p/3fcf8ba18d7f + webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + webSettings.setMediaPlaybackRequiresUserGesture(true); + + webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); + + setLayerType(View.LAYER_TYPE_HARDWARE, null); // setLayerType(View.LAYER_TYPE_SOFTWARE, null);//开启软件加速(在我的MX5上,滑动时会卡顿) -// setLayerType(View.LAYER_TYPE_HARDWARE, null);//开启硬件加速 + // 设置WebView是否应加载图像资源。请注意,此方法控制所有图像的加载,包括使用数据URI方案嵌入的图像。 + webSettings.setLoadsImagesAutomatically(true); // 将图片下载阻塞,然后在浏览器的OnPageFinished事件中设置webView.getSettings().setBlockNetworkImage(false); // 通过图片的延迟载入,让网页能更快地显示。但是会造成懒加载失效,因为懒加载的脚本执行更早,所以认为所有未显示的图片都在同一屏幕内,要开始加载。 // webSettings.setBlockNetworkImage(true); - // 设置在页面装载完成之后自动去加载图片 - webSettings.setLoadsImagesAutomatically(true); + // webSettings.setSupportMultipleWindows(true); // webSettings.setGeolocationEnabled(true); // webSettings.setAppCacheMaxSize(Long.MAX_VALUE); -// webSettings.setDatabaseEnabled(true); // 支持javascript读写db // webSettings.setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY); // webSettings.setPluginState(WebSettings.PluginState.ON_DEMAND); // this.getSettingsExtension().setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY);//extension @@ -196,20 +241,31 @@ public void destroy() { // public void onPageFinished(final WebView view, String url) // 里面启用一个runnable去执行一串的js脚本,如果用户在你脚本没执行完成的时候就关闭了当前界面,系统就会抛出空指针异常。这时候就需要通过去onPageFinished获取webview对象 +// setVisibility(View.GONE); try { - // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错 - getSettings().setJavaScriptEnabled(false); stopLoading(); onPause(); + CookieManager.getInstance().flush(); + + // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错 + getSettings().setJavaScriptEnabled(false); +// clearFormData(); // 清除自动完成填充的表单数据。需要注意的是,该方法仅仅清除当前表单域自动完成填充的表单数据,并不会清除WebView存储到本地的数据。 + clearMatches(); // 清除网页查找的高亮匹配字符。 + clearSslPreferences(); //清除ssl信息。 + clearDisappearingChildren(); + clearAnimation(); + clearHistory(); - clearCache(true); -// WebStorage.getInstance().deleteAllData(); -// destroyDrawingCache(); +// clearCache(true); +// destroyDrawingCache(); // 貌似影响内存回收 + removeAllViews(); + // removeAllViewsInLayout(); 相比而言, removeAllViews() 也调用了removeAllViewsInLayout(), 但是后面还调用了requestLayout(),这个方法是当View的布局发生改变会调用它来更新当前视图, 移除子View会更加彻底. 所以除非必要, 还是推荐使用removeAllViews()这个方法. ViewGroup parent = (ViewGroup) this.getParent(); if (parent != null) { parent.removeView(this); } - removeAllViews(); + setWebChromeClient(null); + // 将缓存中的cookie数据同步到数据库。此调用将阻塞调用者,直到它完成并可能执行I/O。 } catch (Exception e) { KLog.e("报错"); e.printStackTrace(); @@ -220,49 +276,42 @@ public void destroy() { /** * 不要将 base url 设为 null + * 4.4以上版本,使用evaluateJavascript方法调js方法,会有返回值 */ public void loadData(String htmlContent) { - loadDataWithBaseURL(App.webViewBaseUrl, htmlContent, "text/html", "UTF-8", null); +// stopLoading(); +// getSettings().setBlockNetworkImage(true); // 将图片下载阻塞 + loadDataWithBaseURL(App.i().getWebViewBaseUrl(), htmlContent, "text/html", "UTF-8", null); isReadability = false; } -// private int displayMode = Api.RSS; -// private String displayContent; -// public void loadReadability(String htmlContent) { -// displayMode = Api.READABILITY; -// displayContent = htmlContent; -// loadData(htmlContent); -// } -// public void loadRSS(String htmlContent) { -// displayMode = Api.RSS; -// displayContent = htmlContent; -// loadData(htmlContent); -// } -// public void loadLink(String url) { -// displayMode = Api.LINK; -// displayContent = url; -// loadUrl(url); -// } - - -// public void clear() { -// ViewParent parent = this.getParent(); -// if (parent != null) { -// ((ViewGroup) parent).removeView(this); -// } -// stopLoading(); -// clearCache(true); -// clearHistory(); -// removeJavascriptInterface("ImageBridge"); -// post(new Runnable() { -// @Override -// public void run() { -// loadData("", "text/html", "UTF-8"); -// } -// }); -// } + @Override + protected void onScrollChanged(final int l, final int t, final int oldl, + final int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (mOnScrollChangedCallback != null) { + mOnScrollChangedCallback.onScrollY(t - oldt); + } + } + public OnScrollChangedCallback getOnScrollChangedCallback() { + return mOnScrollChangedCallback; + } + + public void setOnScrollChangedCallback( + final OnScrollChangedCallback onScrollChangedCallback) { + mOnScrollChangedCallback = onScrollChangedCallback; + } + + private OnScrollChangedCallback mOnScrollChangedCallback; + + /** + * Impliment in the activity/fragment/view that you want to listen to the webview + */ + public interface OnScrollChangedCallback { + void onScrollY(int dy); + } } diff --git a/app/src/main/java/me/wizos/loread/view/colorful/Colorful.java b/app/src/main/java/me/wizos/loread/view/colorful/Colorful.java index 4a0bc4f..f8d34c4 100644 --- a/app/src/main/java/me/wizos/loread/view/colorful/Colorful.java +++ b/app/src/main/java/me/wizos/loread/view/colorful/Colorful.java @@ -2,11 +2,12 @@ import android.app.Activity; import android.content.res.Resources.Theme; -import android.support.v4.app.Fragment; import android.util.TypedValue; import android.view.View; import android.widget.TextView; +import androidx.fragment.app.Fragment; + import java.util.HashSet; import java.util.Set; @@ -18,161 +19,151 @@ /** * 主题切换控制类 - * + * * @author mrsimple - * */ public final class Colorful { - /** - * Colorful Builder - */ - Builder mBuilder; - - /** - * private constructor - * - * @param builder - */ - private Colorful(Builder builder) { - mBuilder = builder; - } - - /** - * 设置新的主题 - * - * @param newTheme - */ - public void setTheme(int newTheme) { - mBuilder.setTheme(newTheme); - } - - /** - * - * 构建Colorful的Builder对象 - * - * @author mrsimple - * - */ - public static class Builder { - /** - * 存储了视图和属性资源id的关系表 - */ - Set mElements = new HashSet(); - /** - * 目标Activity - */ - Activity mActivity; + /** + * Colorful Builder + */ + Builder mBuilder; + + /** + * private constructor + * + * @param builder + */ + private Colorful(Builder builder) { + mBuilder = builder; + } + + /** + * 设置新的主题 + * + * @param newTheme + */ + public void setTheme(int newTheme) { + mBuilder.setTheme(newTheme); + } + + /** + * 构建Colorful的Builder对象 + * + * @author mrsimple + */ + public static class Builder { + /** + * 存储了视图和属性资源id的关系表 + */ + Set mElements = new HashSet(); + /** + * 目标Activity + */ + Activity mActivity; - /** - * @param activity - */ - public Builder(Activity activity) { - mActivity = activity; - } + /** + * @param activity + */ + public Builder(Activity activity) { + mActivity = activity; + } - /** - * - * @param fragment - */ - public Builder(Fragment fragment) { - mActivity = fragment.getActivity(); - } + /** + * @param fragment + */ + public Builder(Fragment fragment) { + mActivity = fragment.getActivity(); + } - private View findViewById(int viewId) { - return mActivity.findViewById(viewId); - } + private View findViewById(int viewId) { + return mActivity.findViewById(viewId); + } - /** - * 将View id与存储该view背景色的属性进行绑定 - * - * @param viewId - * 控件id - * @param colorId - * 颜色属性id - * @return - */ - public Builder backgroundColor(int viewId, int colorId) { - mElements.add(new ViewBackgroundColorSetter(findViewById(viewId), - colorId)); - return this; - } + /** + * 将View id与存储该view背景色的属性进行绑定 + * + * @param viewId 控件id + * @param colorId 颜色属性id + * @return + */ + public Builder backgroundColor(int viewId, int colorId) { + mElements.add(new ViewBackgroundColorSetter(findViewById(viewId), + colorId)); + return this; + } - /** - * 将View id与存储该view背景Drawable的属性进行绑定 - * - * @param viewId - * 控件id - * @param drawableId - * Drawable属性id - * @return - */ - public Builder backgroundDrawable(int viewId, int drawableId) { - mElements.add(new ViewBackgroundDrawableSetter( - findViewById(viewId), drawableId)); - return this; - } + /** + * 将View id与存储该view背景Drawable的属性进行绑定 + * + * @param viewId 控件id + * @param drawableId Drawable属性id + * @return + */ + public Builder backgroundDrawable(int viewId, int drawableId) { + mElements.add(new ViewBackgroundDrawableSetter( + findViewById(viewId), drawableId)); + return this; + } - /** - * 将TextView id与存储该TextView文本颜色的属性进行绑定 - * - * @param viewId - * TextView或者TextView子类控件的id - * @param colorId - * 颜色属性id - * @return - */ - public Builder textColor(int viewId, int colorId) { - TextView textView = (TextView) findViewById(viewId); - mElements.add(new TextColorSetter(textView, colorId)); - return this; - } + /** + * 将TextView id与存储该TextView文本颜色的属性进行绑定 + * + * @param viewId TextView或者TextView子类控件的id + * @param colorId 颜色属性id + * @return + */ + public Builder textColor(int viewId, int colorId) { + TextView textView = (TextView) findViewById(viewId); + mElements.add(new TextColorSetter(textView, colorId)); + return this; + } // mElements 用于保存 要修改的 Views /** * 用户手动构造并且添加Setter(对于listView来说,只是把旗下某个项的 viewid 和 要改的值 挂钩在一起) * - * @param setter - * 用户自定义的Setter - * @return - */ - public Builder setter(ViewSetter setter) { - mElements.add(setter); - return this; - } + * @param setter 用户自定义的Setter + * @return + */ + public Builder setter(ViewSetter setter) { + mElements.add(setter); + return this; + } - /** - * 设置新的主题 - * - * @param newTheme - */ - protected void setTheme(int newTheme) { - mActivity.setTheme(newTheme); - makeChange(newTheme); + /** + * 设置新的主题 + * + * @param newTheme + */ + protected void setTheme(int newTheme) { + mActivity.setTheme(newTheme); + makeChange(newTheme); refreshStatusBar(); - } + } - /** - * 修改各个视图绑定的属性 - */ - private void makeChange(int themeId) { - Theme curTheme = mActivity.getTheme(); - for (ViewSetter setter : mElements) { - setter.setValue(curTheme, themeId); - } - } + /** + * 修改各个视图绑定的属性 + */ + private void makeChange(int themeId) { + Theme curTheme = mActivity.getTheme(); + for (ViewSetter setter : mElements) { + setter.setValue(curTheme, themeId); + } + } /** - * 我新加的 方法一 - * 刷新 StatusBar - */ - private void refreshStatusBar() { - TypedValue typedValue = new TypedValue(); - Theme theme = mActivity.getTheme(); - theme.resolveAttribute(R.attr.status_bar, typedValue, true); - StatusBarUtil.setColorNoTranslucent(mActivity,mActivity.getResources().getColor(typedValue.resourceId )); + * 我新加的 方法一 + * 刷新 StatusBar + */ + private void refreshStatusBar() { + TypedValue typedValue = new TypedValue(); + Theme theme = mActivity.getTheme(); + theme.resolveAttribute(R.attr.status_bar, typedValue, true); + StatusBarUtil.setColorNoTranslucent(mActivity, mActivity.getResources().getColor(typedValue.resourceId)); // KLog.d("【修改状态栏】" + typedValue.resourceId ); - } + } + // /** // * 我新加的 方法二 @@ -249,17 +240,15 @@ private void refreshStatusBar() { // } - - /** - * 创建Colorful对象 - * - * @return - */ - public Colorful create() { - return new Colorful(this); - } - } - + /** + * 创建Colorful对象 + * + * @return + */ + public Colorful create() { + return new Colorful(this); + } + } } diff --git a/app/src/main/java/me/wizos/loread/view/colorful/StatusBarUtil.java b/app/src/main/java/me/wizos/loread/view/colorful/StatusBarUtil.java index e8cca4b..3772e40 100644 --- a/app/src/main/java/me/wizos/loread/view/colorful/StatusBarUtil.java +++ b/app/src/main/java/me/wizos/loread/view/colorful/StatusBarUtil.java @@ -5,16 +5,17 @@ import android.content.Context; import android.graphics.Color; import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.v4.widget.DrawerLayout; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.LinearLayout; +import androidx.annotation.ColorInt; +import androidx.drawerlayout.widget.DrawerLayout; + /** * Created by Jaeger on 16/2/14. - * + *

          * Email: chjie.jaeger@gmail.com * GitHub: https://github.com/laobie */ @@ -78,7 +79,7 @@ public static void setColorForSwipeBack(Activity activity, int color) { */ public static void setColorForSwipeBack(Activity activity, @ColorInt int color, int statusBarAlpha) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - ViewGroup contentView = ((ViewGroup) activity.findViewById(android.R.id.content)); + ViewGroup contentView = activity.findViewById(android.R.id.content); contentView.setPadding(0, getStatusBarHeight(activity), 0, 0); contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); setTransparentForWindow(activity); @@ -107,7 +108,7 @@ public static void setColorDiff(Activity activity, @ColorInt int color) { return; } transparentStatusBar(activity); - ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + ViewGroup contentView = activity.findViewById(android.R.id.content); // 移除半透明矩形,以免叠加 if (contentView.getChildCount() > 1) { contentView.getChildAt(1).setBackgroundColor(color); @@ -119,7 +120,7 @@ public static void setColorDiff(Activity activity, @ColorInt int color) { /** * 使状态栏半透明 - * + *

          * 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity @@ -130,7 +131,7 @@ public static void setTranslucent(Activity activity) { /** * 使状态栏半透明 - * + *

          * 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity @@ -146,7 +147,7 @@ public static void setTranslucent(Activity activity, int statusBarAlpha) { /** * 针对根布局是 CoordinatorLayout, 使状态栏半透明 - * + *

          * 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity @@ -175,7 +176,7 @@ public static void setTransparent(Activity activity) { /** * 使状态栏透明(5.0以上半透明效果,不建议使用) - * + *

          * 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity @@ -220,7 +221,7 @@ public static void setColorNoTranslucentForDrawerLayout(Activity activity, Drawe * @param statusBarAlpha 状态栏透明度 */ public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color, - int statusBarAlpha) { + int statusBarAlpha) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } @@ -243,8 +244,8 @@ public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawe // 内容布局不是 LinearLayout 时,设置padding top if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { contentLayout.getChildAt(1) - .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(), - contentLayout.getPaddingRight(), contentLayout.getPaddingBottom()); + .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(), + contentLayout.getPaddingRight(), contentLayout.getPaddingBottom()); } // 设置属性 setDrawerLayoutProperty(drawerLayout, contentLayout); @@ -462,7 +463,7 @@ private static void clearPreviousSetting(Activity activity) { * @param statusBarAlpha 透明值 */ private static void addTranslucentView(Activity activity, int statusBarAlpha) { - ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + ViewGroup contentView = activity.findViewById(android.R.id.content); if (contentView.getChildCount() > 1) { contentView.getChildAt(1).setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0)); } else { @@ -481,7 +482,7 @@ private static StatusBarView createStatusBarView(Activity activity, @ColorInt in // 绘制一个和状态栏一样高的矩形 StatusBarView statusBarView = new StatusBarView(activity); LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setLayoutParams(params); statusBarView.setBackgroundColor(color); return statusBarView; @@ -499,7 +500,7 @@ private static StatusBarView createStatusBarView(Activity activity, @ColorInt in // 绘制一个和状态栏一样高的矩形 StatusBarView statusBarView = new StatusBarView(activity); LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setLayoutParams(params); statusBarView.setBackgroundColor(calculateStatusColor(color, alpha)); return statusBarView; @@ -509,12 +510,12 @@ private static StatusBarView createStatusBarView(Activity activity, @ColorInt in * 设置根布局参数 */ private static void setRootView(Activity activity) { - ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content); + ViewGroup parent = activity.findViewById(android.R.id.content); for (int i = 0, count = parent.getChildCount(); i < count; i++) { View childView = parent.getChildAt(i); if (childView instanceof ViewGroup) { childView.setFitsSystemWindows(true); - ((ViewGroup)childView).setClipToPadding(true); + ((ViewGroup) childView).setClipToPadding(true); } } } @@ -526,11 +527,11 @@ private static void setTransparentForWindow(Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow() - .getDecorView() - .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + .getDecorView() + .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { activity.getWindow() - .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } } @@ -559,7 +560,7 @@ private static StatusBarView createTranslucentStatusBarView(Activity activity, i // 绘制一个和状态栏一样高的矩形 StatusBarView statusBarView = new StatusBarView(activity); LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setLayoutParams(params); statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); return statusBarView; diff --git a/app/src/main/java/me/wizos/loread/view/colorful/StatusBarView.java b/app/src/main/java/me/wizos/loread/view/colorful/StatusBarView.java index df4f18d..7263488 100644 --- a/app/src/main/java/me/wizos/loread/view/colorful/StatusBarView.java +++ b/app/src/main/java/me/wizos/loread/view/colorful/StatusBarView.java @@ -6,7 +6,7 @@ /** * Created by Jaeger on 16/6/8. - * + *

          * Email: chjie.jaeger@gmail.com * GitHub: https://github.com/laobie */ diff --git a/app/src/main/java/me/wizos/loread/view/colorful/setter/ViewGroupSetter.java b/app/src/main/java/me/wizos/loread/view/colorful/setter/ViewGroupSetter.java index 8395bd1..688bbde 100644 --- a/app/src/main/java/me/wizos/loread/view/colorful/setter/ViewGroupSetter.java +++ b/app/src/main/java/me/wizos/loread/view/colorful/setter/ViewGroupSetter.java @@ -1,11 +1,12 @@ package me.wizos.loread.view.colorful.setter; import android.content.res.Resources.Theme; -import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; +import androidx.recyclerview.widget.RecyclerView; + import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -61,8 +62,7 @@ public ViewGroupSetter childViewBgColor(int viewId, int colorId) { * @return */ public ViewGroupSetter childViewBgDrawable(int viewId, int drawableId) { - mItemViewSetters.add(new ViewBackgroundDrawableSetter(viewId, - drawableId)); + mItemViewSetters.add(new ViewBackgroundDrawableSetter(viewId, drawableId)); return this; } @@ -102,9 +102,8 @@ public void setValue(Theme newTheme, int themeId) { * @return */ private View findViewById(View rootView, int viewId) { - View targetView = rootView.findViewById(viewId); // Log.d("", "### viewgroup find view : " + targetView); - return targetView; + return rootView.findViewById(viewId); } /** @@ -114,8 +113,7 @@ private View findViewById(View rootView, int viewId) { * @param newTheme * @param themeId */ - private void changeChildenAttrs(ViewGroup viewGroup, Theme newTheme, - int themeId) { + private void changeChildenAttrs(ViewGroup viewGroup, Theme newTheme, int themeId) { int childCount = viewGroup.getChildCount(); for (int i = 0; i < childCount; i++) { View childView = viewGroup.getChildAt(i); @@ -168,18 +166,21 @@ private void clearListViewRecyclerBin(View rootView) { } private void clearRecyclerViewRecyclerBin(View rootView) { +// KLog.e("", "### 准备 清空RecyclerView的Recycer "); if (rootView instanceof RecyclerView) { try { - Field localField = RecyclerView.class - .getDeclaredField("mRecycler"); + Field localField = RecyclerView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName( - "android.support.v7.widget.RecyclerView$Recycler") - .getDeclaredMethod("clear"); + "androidx.recyclerview.widget.RecyclerView$Recycler") + .getDeclaredMethod("clear", new Class[0]); localMethod.setAccessible(true); - localMethod.invoke(localField.get(rootView)); -// Log.e("", "### 清空RecyclerView的Recycer "); + localMethod.invoke(localField.get(rootView), new Object[0]); + ((RecyclerView) rootView).getRecycledViewPool().clear(); + rootView.invalidate(); + + // KLog.e("", "### 清空RecyclerView的Recycer "); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { diff --git a/app/src/main/java/me/wizos/loread/utils/FastScrollDelegate.java b/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollDelegate.java similarity index 99% rename from app/src/main/java/me/wizos/loread/utils/FastScrollDelegate.java rename to app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollDelegate.java index f22b3f3..fa7d901 100644 --- a/app/src/main/java/me/wizos/loread/utils/FastScrollDelegate.java +++ b/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollDelegate.java @@ -1,4 +1,4 @@ -package me.wizos.loread.utils; +package me.wizos.loread.view.fastscroll; import android.annotation.SuppressLint; import android.content.Context; @@ -15,7 +15,6 @@ import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.StateListDrawable; -import android.support.v4.view.ViewCompat; import android.text.TextUtils.TruncateAt; import android.util.Log; import android.util.TypedValue; @@ -28,6 +27,8 @@ import android.widget.PopupWindow; import android.widget.TextView; +import androidx.core.view.ViewCompat; + /** * 实现 ListView ,WebView 等的快速滚动条 * https://github.com/Mixiaoxiao/FastScroll-Everywhere FastScrollDelegate diff --git a/app/src/main/java/me/wizos/loread/view/ListView/FastScrollListView.java b/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollListView.java similarity index 98% rename from app/src/main/java/me/wizos/loread/view/ListView/FastScrollListView.java rename to app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollListView.java index 598304b..db686e3 100644 --- a/app/src/main/java/me/wizos/loread/view/ListView/FastScrollListView.java +++ b/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollListView.java @@ -1,4 +1,4 @@ -package me.wizos.loread.view.ListView; +package me.wizos.loread.view.fastscroll; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -10,8 +10,6 @@ import android.view.View; import android.widget.ListView; -import me.wizos.loread.utils.FastScrollDelegate; - /** * https://github.com/Mixiaoxiao/FastScroll-Everywhere FastScrollListView * diff --git a/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollRecyclerView.java b/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollRecyclerView.java new file mode 100644 index 0000000..3042e13 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/fastscroll/FastScrollRecyclerView.java @@ -0,0 +1,151 @@ +package me.wizos.loread.view.fastscroll; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * https://github.com/Mixiaoxiao/FastScroll-Everywhere FastScrollRecyclerView + * + * @author Mixiaoxiao 2016-08-31 + */ +public class FastScrollRecyclerView extends RecyclerView implements FastScrollDelegate.FastScrollable { + + private FastScrollDelegate mFastScrollDelegate; + + // =========================================================== + // Constructors + // =========================================================== + + public FastScrollRecyclerView(Context context) { + super(context); + createFastScrollDelegate(context); + } + + public FastScrollRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + createFastScrollDelegate(context); + } + + public FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + createFastScrollDelegate(context); + } + + // =========================================================== + // createFastScrollDelegate + // =========================================================== + + private void createFastScrollDelegate(Context context) { + mFastScrollDelegate = new FastScrollDelegate.Builder(this).build(); + } + + // =========================================================== + // Delegate + // =========================================================== + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mFastScrollDelegate.onInterceptTouchEvent(ev)) { + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mFastScrollDelegate.onTouchEvent(event)) { + return true; + } + return super.onTouchEvent(event); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mFastScrollDelegate.onAttachedToWindow(); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (mFastScrollDelegate != null) { + mFastScrollDelegate.onVisibilityChanged(changedView, visibility); + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mFastScrollDelegate.onWindowVisibilityChanged(visibility); + } + + @Override + protected boolean awakenScrollBars() { + return mFastScrollDelegate.awakenScrollBars(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + mFastScrollDelegate.dispatchDrawOver(canvas); + } + + // =========================================================== + // FastScrollable IMPL, ViewInternalSuperMethods + // =========================================================== + + @Override + public void superOnTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + } + + @Override + public int superComputeVerticalScrollExtent() { + return super.computeVerticalScrollExtent(); + } + + @Override + public int superComputeVerticalScrollOffset() { + return super.computeVerticalScrollOffset(); + } + + @Override + public int superComputeVerticalScrollRange() { + return super.computeVerticalScrollRange(); + } + + @Override + public View getFastScrollableView() { + return this; + } + + /** + * @deprecated use {@link #getFastScrollDelegate()} instead + */ + public FastScrollDelegate getDelegate() { + return getFastScrollDelegate(); + } + + @Override + public FastScrollDelegate getFastScrollDelegate() { + return mFastScrollDelegate; + } + + @Override + public void setNewFastScrollDelegate(FastScrollDelegate newDelegate) { + if (newDelegate == null) { + throw new IllegalArgumentException("setNewFastScrollDelegate must NOT be NULL."); + } + mFastScrollDelegate.onDetachedFromWindow(); + mFastScrollDelegate = newDelegate; + newDelegate.onAttachedToWindow(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/fastscroll/ListViewS.java b/app/src/main/java/me/wizos/loread/view/fastscroll/ListViewS.java new file mode 100644 index 0000000..c8b250d --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/fastscroll/ListViewS.java @@ -0,0 +1,273 @@ +//package me.wizos.loreadx.view.listview; +// +//import android.content.Context; +//import android.os.Handler; +//import android.os.Message; +//import android.util.AttributeSet; +//import android.view.MotionEvent; +//import android.view.View; +//import android.widget.AdapterView; +// +//import com.ditclear.swipelayout.SwipeDragLayout; +//import com.socks.library.KLog; +// +///** +// * Created by Wizos on 2017/12/24. +// */ +// +//public class ListViewS extends FastScrollListView implements Handler.Callback, SwipeDragLayout.SwipeListener { +// +// /* handler */ +// private Handler mHandler; +// +// public ListViewS(Context context) { +// this(context, null); +// } +// +// public ListViewS(Context context, AttributeSet attrs) { +// this(context, attrs, 0); +// } +// +// public ListViewS(Context context, AttributeSet attrs, int defStyleAttr) { +// super(context, attrs, defStyleAttr); +// mHandler = new Handler(this); +// } +// +// +// /** +// * 自己写的长点击事件 +// */ +// /* Handler 的 Message 信息 */ +// private static final int MSG_WHAT_LONG_CLICK = 1; +// +// /* onTouch里面的状态 */ +// private static final int STATE_NOTHING = -1;//抬起状态 +// private static final int STATE_DOWN = 0;//按下状态 +// private static final int STATE_LONG_CLICK = 1;//长点击状态 +// // private static final int STATE_SCROLL = 2;//SCROLL状态 +// private static final int STATE_LONG_CLICK_FINISH = 3;//长点击已经触发完成 +// // private static final int STATE_MORE_FINGERS = 4;//多个手指 +// private int mState = STATE_NOTHING; +// private OnListItemLongClickListener mOnListItemLongClickListener; +// +// public void setOnListItemLongClickListener(OnListItemLongClickListener listener) { +// mOnListItemLongClickListener = listener; +// } +// +// public interface OnListItemLongClickListener { +// void onListItemLongClick(View view, int position); +// } +// +// @Override +// public boolean handleMessage(Message msg) { +// switch (msg.what) { +// case MSG_WHAT_LONG_CLICK: +// //如果得到msg的时候state状态是Long Click的话 +// if (mState == STATE_DOWN || mState == STATE_LONG_CLICK) { +// //改为long click触发完成 +// mState = STATE_LONG_CLICK_FINISH; +// //得到长点击的位置 +// int position = msg.arg1; +// //找到那个位置的view +// View view = getChildAt(position - getFirstVisiblePosition()); +// //如果设置了监听器的话,就触发 +// if (mOnListItemLongClickListener != null && position == pointToPosition(lastX, lastY)) { +// mOnListItemLongClickListener.onListItemLongClick(view, position); +// KLog.e("==" + msg.what); +//// mVibrator.vibrate(100); // 触发震动 +// } +// } +// break; +// default: +// break; +// } +// return true; +// } +// +// /* 手指放下的坐标 */ +// private int downX; +// private int downY; +// /* Handler 发送message需要延迟的时间 */ +// private static final long CLICK_LONG_TRIGGER_TIME = 500;//1s +// +// /** +// * remove message +// */ +// private void removeLongClickMessage() { +// if (mHandler.hasMessages(MSG_WHAT_LONG_CLICK)) { +// mHandler.removeMessages(MSG_WHAT_LONG_CLICK); +// } +// } +// +// /** +// * send message +// */ +// private void sendLongClickMessage(int position) { +// if (!mHandler.hasMessages(MSG_WHAT_LONG_CLICK)) { +// Message message = new Message(); +// message.what = MSG_WHAT_LONG_CLICK; +// message.arg1 = position; +// mHandler.sendMessageDelayed(message, CLICK_LONG_TRIGGER_TIME); +// } +// } +// +//// @Override +//// public boolean onInterceptTouchEvent(MotionEvent ev) { +//// switch (ev.getAction()) { +//// case MotionEvent.ACTION_DOWN: +//// break; +//// case MotionEvent.ACTION_MOVE: +//// // 容差值大概是24,再加上60 +//// if (fingerLeftAndRightMove(ev)) { +//// return false; +//// } +//// break; +//// } +//// return super.onInterceptTouchEvent(ev); +//// } +// +// private int slideItemPosition = -1; +// private int lastX; +// private int lastY; +// +// @Override +// public boolean dispatchTouchEvent(MotionEvent ev) { +// lastX = (int) ev.getX(); +// lastY = (int) ev.getY(); +// switch (ev.getAction()) { // & MotionEvent.ACTION_MASK +// case MotionEvent.ACTION_DOWN: +// //获取出坐标来 +// downX = (int) ev.getX(); +// downY = (int) ev.getY(); +// //当前state状态为按下 +// mState = STATE_DOWN; +// sendLongClickMessage(pointToPosition(downX, downY)); // FIXME: 2016/5/4 【添加】修复 长按 bug +// break; +// // 这个是实现多点的关键,当屏幕检测到有多个手指同时按下之后,就触发了这个事件 +// case MotionEvent.ACTION_POINTER_DOWN: +// removeLongClickMessage(); +//// mState = STATE_MORE_FINGERS; +// //消耗掉,不传递下去了 +// return true; +// +// case MotionEvent.ACTION_MOVE: +// if (fingerNotMove(ev)) {//手指的范围在50以内 +//// removeLongClickMessage(); +//// return true; +// } else if (fingerLeftAndRightMove(ev)) { +// removeLongClickMessage(); +// //将当前想要滑动哪一个传递给wrapperAdapter +// int position = pointToPosition(downX, downY); +// if (position != AdapterView.INVALID_POSITION) { +// slideItemPosition = position; +// } +// } else { +// removeLongClickMessage(); +// } +// break; +// case MotionEvent.ACTION_UP: +// case MotionEvent.ACTION_CANCEL: +// removeLongClickMessage(); +// break; +// default: +// break; +// } +// return super.dispatchTouchEvent(ev); +// } +// +// /* 手指滑动的最短距离 */ +// private int mShortestDistance = 25; +// +// /** +// * 上下左右不能超出50 +// * +// * @param ev +// * @return +// */ +// private boolean fingerNotMove(MotionEvent ev) { +// return (downX - ev.getX() < mShortestDistance && downX - ev.getX() > -mShortestDistance && +// downY - ev.getY() < mShortestDistance && downY - ev.getY() > -mShortestDistance); +// } +// +// /** +// * 左右得超出50,上下不能超出50 +// * +// * @param ev +// * @return +// */ +// private boolean fingerLeftAndRightMove(MotionEvent ev) { +// return ((ev.getX() - downX > mShortestDistance || ev.getX() - downX < -mShortestDistance) && +// ev.getY() - downY < mShortestDistance && ev.getY() - downY > -mShortestDistance); +// } +// +// /** +// * 设置列表项左右滑动时的监听器 +// * +// * @param onItemSlideListener +// */ +// public void setItemSlideListener(OnItemSlideListener onItemSlideListener) { +// mOnItemSlideListener = onItemSlideListener; +// } +// +// private OnItemSlideListener mOnItemSlideListener; +// +// public interface OnItemSlideListener { +// // int onSlideOpen(View view, int position, int direction); +// // FIXME: 2016/5/4 【实现划开自动返回】把返回类型由 void 改为 int +// void onUpdate(View view, int position, float offset); +// +// void onCloseLeft(View view, int position, int direction); +// +// void onCloseRight(View view, int position, int direction); +// +// void onClick(View view, int position); +// +// void log(String layout); +// } +// +// +// @Override +// public void onUpdate(View view, float offsetRatio, int offset) { +// if (mOnItemSlideListener != null) { +// mOnItemSlideListener.onUpdate(view, slideItemPosition, offsetRatio); +// } +// } +// +// @Override +// public void onOpened(View view) { +// +// } +// +// @Override +// public void onClosed(View view) { +// +// } +// +// @Override +// public void onCloseLeft(View view) { +// if (mOnItemSlideListener != null) { +// mOnItemSlideListener.onCloseLeft(view, slideItemPosition, SwipeDragLayout.DIRECTION_LEFT); +// } +// } +// +// @Override +// public void onCloseRight(View view) { +// if (mOnItemSlideListener != null) { +//// KLog.e("关闭右侧" + (lastX - downX) + "==" + (lastY - downY)); +// mOnItemSlideListener.onCloseRight(view, slideItemPosition, SwipeDragLayout.DIRECTION_RIGHT); +// } +// } +// +// @Override +// public void onClick(View view) { +// if (mOnItemSlideListener != null) { +// int position = pointToPosition(downX, downY); +// mOnItemSlideListener.onClick(view, position); +// } +// } +// +// @Override +// public void log(String layout) { +// mOnItemSlideListener.log(layout); +// } +//} diff --git a/app/src/main/java/me/wizos/loread/view/slideback/SlideBack.java b/app/src/main/java/me/wizos/loread/view/slideback/SlideBack.java new file mode 100644 index 0000000..1f9377a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/SlideBack.java @@ -0,0 +1,85 @@ +package me.wizos.loread.view.slideback; + +import android.app.Activity; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.WeakHashMap; + +/** + * author : ParfoisMeng + * time : 2018/12/19 + * desc : SlideBack使用类 + */ +public class SlideBack { + // 使用WeakHashMap防止内存泄漏 + private static WeakHashMap map = new WeakHashMap<>(); + +// /** +// * 注册 +// * +// * @param activity 目标Act +// * @param callBack 回调 +// */ +// public static void register(Activity activity, SlideBackCallBack callBack) { +// register(activity, false, callBack); +// } +// +// /** +// * 注册 +// * +// * @param activity 目标Act +// * @param haveScroll 页面是否有滑动 +// * @param callBack 回调 +// */ +// public static void register(Activity activity, boolean haveScroll, SlideBackCallBack callBack) { +// with(activity).haveScroll(haveScroll).callBack(callBack).register(); +// } + + /** + * 注销 + * + * @param activity 目标Act + */ + public static void unregister(Activity activity) { + SlideBackManager slideBack = map.get(activity); + if (null != slideBack) { + slideBack.unregister(); + } + map.remove(activity); + } + + /** + * 构建侧滑管理器 - 用于更丰富的自定义配置 + * + * @param activity 目标Act + * @return 构建管理器 + */ + public static SlideBackManager with(Activity activity) { + SlideBackManager manager = new SlideBackManager(activity); + map.put(activity, manager); + return manager; + } + + /** + * 侧滑返回模式 左 + */ + public static final int EDGE_LEFT = 0x0000; + + /** + * 侧滑返回模式 右 + */ + public static final int EDGE_RIGHT = 0x0001; + + /** + * 侧滑返回模式 左右皆可 + */ + public static final int EDGE_BOTH = 0x0002; + + @IntDef({EDGE_LEFT, EDGE_RIGHT, EDGE_BOTH}) + @Retention(RetentionPolicy.SOURCE) + @interface EdgeMode { + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/slideback/SlideBackManager.java b/app/src/main/java/me/wizos/loread/view/slideback/SlideBackManager.java new file mode 100644 index 0000000..836895a --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/SlideBackManager.java @@ -0,0 +1,359 @@ +package me.wizos.loread.view.slideback; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.socks.library.KLog; + +import me.wizos.loread.R; +import me.wizos.loread.view.slideback.callback.SlideCallBack; +import me.wizos.loread.view.slideback.widget.SlideBackIconView; +import me.wizos.loread.view.slideback.widget.SlideBackInterceptLayout; + +import static me.wizos.loread.view.slideback.SlideBack.EDGE_BOTH; +import static me.wizos.loread.view.slideback.SlideBack.EDGE_LEFT; +import static me.wizos.loread.view.slideback.SlideBack.EDGE_RIGHT; + +/** + * author : ParfoisMeng + * time : 2018/12/19 + * desc : SlideBack管理器 + */ +public class SlideBackManager { + private SlideBackIconView slideBackIconViewLeft; + private SlideBackIconView slideBackIconViewRight; + + private Activity activity; + private boolean haveScroll; + + private SlideCallBack callBack; + + private float backViewHeight; // 控件高度 + private float arrowSize; // 箭头图标大小 + private float maxSlideLength; // 最大拉动距离 + + // FIXME: 2019/5/1 + private float sideSlideStartLength; // 侧滑时开始响应的距离 + + private float sideSlideLength; // 侧滑响应距离 + private float dragRate; // 阻尼系数 + + private boolean isAllowEdgeLeft; // 使用左侧侧滑 + private boolean isAllowEdgeRight; // 使用右侧侧滑 + + private float screenWidth; // 屏幕宽 + + SlideBackManager(Activity activity) { + this.activity = activity; + haveScroll = false; + + // 获取屏幕信息,初始化控件设置 + DisplayMetrics dm = activity.getResources().getDisplayMetrics(); + screenWidth = dm.widthPixels; + + backViewHeight = dm.heightPixels / 4f; // 高度默认 屏高/4 + arrowSize = dp2px(5); // 箭头大小默认 5dp + maxSlideLength = screenWidth / 12; // 最大宽度默认 屏宽/12 + + sideSlideLength = maxSlideLength / 2; // 侧滑响应距离默认 控件最大宽度/2 + sideSlideStartLength = sideSlideLength / 2; + dragRate = 3; // 阻尼系数默认 3 + + // 侧滑返回模式 默认:左 + isAllowEdgeLeft = true; + isAllowEdgeRight = false; + } + + /** + * 是否包含滑动控件 默认false + */ + public SlideBackManager haveScroll(boolean haveScroll) { + this.haveScroll = haveScroll; + return this; + } + +// /** +// * 回调 +// */ +// public SlideBackManager callBack(SlideBackCallBack callBack) { +// this.callBack = new SlideCallBack(callBack) { +// @Override +// public void onSlide(int edgeFrom) { +// onSlideBack(); +// } +// @Override +// public void onViewSlide(int edgeFrom,int xposition) { +// } +// }; +// return this; +// } + + /** + * 回调 适用于新的左右模式 + */ + public SlideBackManager callBack(SlideCallBack callBack) { + this.callBack = callBack; + return this; + } + + /** + * 控件高度 默认屏高/4 + */ + public SlideBackManager viewHeight(float backViewHeightDP) { + this.backViewHeight = dp2px(backViewHeightDP); + return this; + } + + /** + * 箭头大小 默认5dp + */ + public SlideBackManager arrowSize(float arrowSizeDP) { + this.arrowSize = dp2px(arrowSizeDP); + return this; + } + + /** + * 最大拉动距离(控件最大宽度) 默认屏宽/12 + */ + public SlideBackManager maxSlideLength(float maxSlideLengthDP) { + this.maxSlideLength = dp2px(maxSlideLengthDP); + return this; + } + + /** + * 侧滑响应距离 默认控件最大宽度/2 + */ + public SlideBackManager sideSlideLength(float sideSlideLengthDP) { + this.sideSlideLength = dp2px(sideSlideLengthDP); + return this; + } + + /** + * 阻尼系数 默认3(越小越灵敏) + */ + public SlideBackManager dragRate(float dragRate) { + this.dragRate = dragRate; + return this; + } + + /** + * 边缘侧滑模式 默认左 + */ + public SlideBackManager edgeMode(@SlideBack.EdgeMode int edgeMode) { + switch (edgeMode) { + case EDGE_LEFT: + isAllowEdgeLeft = true; + isAllowEdgeRight = false; + break; + case EDGE_RIGHT: + isAllowEdgeLeft = false; + isAllowEdgeRight = true; + break; + case EDGE_BOTH: + isAllowEdgeLeft = true; + isAllowEdgeRight = true; + break; + default: + throw new RuntimeException("未定义的边缘侧滑模式值:EdgeMode = " + edgeMode); + } + return this; + } + + + /** + * 需要使用滑动的页面注册 + */ + @SuppressLint("ClickableViewAccessibility") + public void register() { + if (isAllowEdgeLeft) { + // 初始化SlideBackIconView 左侧 + slideBackIconViewLeft = new SlideBackIconView(activity); + slideBackIconViewLeft.setBackViewHeight(backViewHeight); + slideBackIconViewLeft.setArrowSize(arrowSize); + slideBackIconViewLeft.setMaxSlideLength(maxSlideLength); + } + if (isAllowEdgeRight) { + // 初始化SlideBackIconView - Right + slideBackIconViewRight = new SlideBackIconView(activity); + slideBackIconViewRight.setBackViewHeight(backViewHeight); + slideBackIconViewRight.setArrowSize(arrowSize); + slideBackIconViewRight.setMaxSlideLength(maxSlideLength); + // 右侧侧滑 需要旋转180° + slideBackIconViewRight.setRotationY(180); + } + + + // 获取decorView并设置OnTouchListener监听 + FrameLayout container = (FrameLayout) activity.getWindow().getDecorView().findViewById(R.id.art_slide_layout); + if (haveScroll) { + SlideBackInterceptLayout interceptLayout = new SlideBackInterceptLayout(activity); + //interceptLayout.setSideSlideLength(screenWidth, sideSlideLength); + + float[] triggerZone1 = new float[2]; + triggerZone1[0] = sideSlideLength / 2; + triggerZone1[1] = maxSlideLength; + float[] triggerZone2 = new float[2]; + triggerZone2[0] = screenWidth - sideSlideLength / 2; + triggerZone2[1] = screenWidth - maxSlideLength; + interceptLayout.addXTriggerZone(triggerZone1); + interceptLayout.addXTriggerZone(triggerZone2); + + addInterceptLayout(container, interceptLayout); + } + + if (isAllowEdgeLeft) { + container.addView(slideBackIconViewLeft); + } + if (isAllowEdgeRight) { + container.addView(slideBackIconViewRight); + } + for (int i = 0, x = container.getChildCount(); i < x; i++) { + KLog.e(" 子视图:" + container.getChildAt(i)); + } +// KLog.e(" 添加箭头:" + slideBackIconViewLeft.getRight() + " , " + slideBackIconViewRight.getRight()); + KLog.e(" 是否要添加箭头:" + isAllowEdgeLeft + " , " + isAllowEdgeRight); + + container.setOnTouchListener(new View.OnTouchListener() { + private boolean isSideSlideLeft = false; // 是否从左边边缘开始滑动 + private boolean isSideSlideRight = false; // 是否从右边边缘开始滑动 + private float downX = 0; // 按下的X轴坐标 + private float moveXLength = 0; // 位移的X轴距离 + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: // 按下 + // 更新按下点的X轴坐标 + downX = event.getRawX(); + +// // 检验是否从边缘开始滑动,区分左右 +// if (isAllowEdgeLeft && downX <= sideSlideLength) { +// isSideSlideLeft = true; +// } else if (isAllowEdgeRight && downX >= screenWidth - sideSlideLength) { +// isSideSlideRight = true; +// } + // 检验是否从边缘开始滑动,区分左右 + if (isAllowEdgeLeft && downX >= sideSlideLength / 2 && downX <= maxSlideLength) { + isSideSlideLeft = true; + } else if (isAllowEdgeRight && downX >= (screenWidth - maxSlideLength) && downX <= (screenWidth - sideSlideLength / 2)) { + isSideSlideRight = true; + } +// KLog.e(" 是否要添加箭头A:" + isAllowEdgeLeft + " , " + isAllowEdgeRight); +// KLog.e(" 是否要添加箭头B:" + (downX <= sideSlideLength) + " , " + (downX >= screenWidth - sideSlideLength )); +// KLog.e(" 是否要添加箭头C:" + isSideSlideLeft + " , " + isSideSlideRight); + break; + case MotionEvent.ACTION_MOVE: // 移动 + if (isSideSlideLeft || isSideSlideRight) { + // 从边缘开始滑动 + // 获取X轴位移距离 + moveXLength = Math.abs(event.getRawX() - downX); + if (moveXLength / dragRate <= maxSlideLength) { + // 如果位移距离在可拉动距离内,更新SlideBackIconView的当前拉动距离并重绘,区分左右 + if (isAllowEdgeLeft && isSideSlideLeft) { + slideBackIconViewLeft.updateSlideLength(moveXLength / dragRate); + callBack.onViewSlide(EDGE_LEFT, (int) (moveXLength / dragRate)); + } else if (isAllowEdgeRight && isSideSlideRight) { + slideBackIconViewRight.updateSlideLength(moveXLength / dragRate); + callBack.onViewSlide(EDGE_RIGHT, (int) (moveXLength / dragRate)); + } + } + + // 根据Y轴位置给SlideBackIconView定位 + if (isAllowEdgeLeft && isSideSlideLeft) { + setSlideBackPosition(slideBackIconViewLeft, (int) (event.getRawY())); + } else if (isAllowEdgeRight && isSideSlideRight) { + setSlideBackPosition(slideBackIconViewRight, (int) (event.getRawY())); + } + } + break; + case MotionEvent.ACTION_UP: // 抬起 + // 是从边缘开始滑动 且 抬起点的X轴坐标大于某值(默认3倍最大滑动长度) 且 回调不为空 + if ((isSideSlideLeft || isSideSlideRight) && moveXLength / dragRate >= maxSlideLength && null != callBack) { + // 区分左右 + callBack.onSlide(isSideSlideLeft ? EDGE_LEFT : EDGE_RIGHT); + } + + // 恢复SlideBackIconView的状态 + if (isAllowEdgeLeft && isSideSlideLeft) { + slideBackIconViewLeft.updateSlideLength(0); + callBack.onViewSlide(EDGE_LEFT, 0); + } else if (isAllowEdgeRight && isSideSlideRight) { + slideBackIconViewRight.updateSlideLength(0); + callBack.onViewSlide(EDGE_RIGHT, 0); + } + + // 从边缘开始滑动结束 + isSideSlideLeft = false; + isSideSlideRight = false; + break; + default: + break; + } + return isSideSlideLeft || isSideSlideRight; + } + }); + } + + /** + * 页面销毁时记得解绑 + * 其实就是置空防止内存泄漏 + */ + @SuppressLint("ClickableViewAccessibility") + void unregister() { +// FrameLayout container = (FrameLayout) activity.getWindow().getDecorView(); +// if (haveScroll) removeInterceptLayout(container); +// container.removeView(slideBackIconViewLeft); +// container.setOnTouchListener(null); + + activity = null; + callBack = null; + slideBackIconViewLeft = null; + slideBackIconViewRight = null; + } + + /** + * 给根布局包上一层事件拦截处理Layout + */ + private void addInterceptLayout(ViewGroup decorView, SlideBackInterceptLayout interceptLayout) { + View rootLayout = decorView.getChildAt(0); // 取出根布局 + decorView.removeView(rootLayout); // 先移除根布局 + // 用事件拦截处理Layout将原根布局包起来,再添加回去 + interceptLayout.addView(rootLayout, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + decorView.addView(interceptLayout); + } + + /** + * 将根布局还原,移除SlideBackInterceptLayout + */ + private void removeInterceptLayout(ViewGroup decorView) { + FrameLayout rootLayout = (FrameLayout) decorView.getChildAt(0); // 取出根布局 + decorView.removeView(rootLayout); // 先移除根布局 + // 将根布局的第一个布局(原根布局)取出放回decorView + View oriLayout = rootLayout.getChildAt(0); + rootLayout.removeView(oriLayout); + decorView.addView(oriLayout); + } + + /** + * 给SlideBackIconView设置topMargin,起到定位效果 + * + * @param view SlideBackIconView + * @param position 触点位置 + */ + private void setSlideBackPosition(SlideBackIconView view, int position) { + // 触点位置减去SlideBackIconView一半高度即为topMargin + int topMargin = (int) (position - (view.getBackViewHeight() / 2)); + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(view.getLayoutParams()); + layoutParams.topMargin = topMargin; + view.setLayoutParams(layoutParams); + } + + private float dp2px(float dpValue) { + return dpValue * activity.getResources().getDisplayMetrics().density + 0.5f; + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/slideback/SlideLayout.java b/app/src/main/java/me/wizos/loread/view/slideback/SlideLayout.java new file mode 100644 index 0000000..95c1597 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/SlideLayout.java @@ -0,0 +1,425 @@ +package me.wizos.loread.view.slideback; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; + +import androidx.annotation.ColorInt; + +import java.lang.ref.WeakReference; + +import me.wizos.loread.view.slideback.callback.SlideCallBack; +import me.wizos.loread.view.slideback.widget.SlideBackIconView; + +import static me.wizos.loread.utils.ScreenUtil.dp2px; +import static me.wizos.loread.view.slideback.SlideBack.EDGE_BOTH; +import static me.wizos.loread.view.slideback.SlideBack.EDGE_LEFT; +import static me.wizos.loread.view.slideback.SlideBack.EDGE_RIGHT; + + +/** + * @author ditclear on 16/7/12. 可滑动的layout extends FrameLayout + * https://github.com/ditclear/TimeLine/blob/master/swipelayout/src/main/java/com/ditclear/swipelayout/SwipeDragLayout.java + * 实现主页的左右滑动已读未读 + */ +public class SlideLayout extends FrameLayout { + private Context context; + private int mScaledTouchSlop; + private SlideBackIconView slideBackIconViewLeft; + private SlideBackIconView slideBackIconViewRight; + + private SlideCallBack callBack; + + private float backViewHeight; // 控件高度 + private float arrowSize; // 箭头图标大小 + private int arrowColor; // 箭头图标大小 + private float maxSlideLength; // 最大拉动距离 + + // FIXME: 2019/5/1 + private float leftViewTriggerStart; // 侧滑时开始响应的距离 + private float leftViewTriggerEnd; + private float rightViewTriggerStart; // 侧滑时开始响应的距离 + private float rightViewTriggerEnd; + + private float sideSlideLength; // 侧滑响应距离 + private float dragRate; // 阻尼系数 + + private boolean isAllowEdgeLeft; // 使用左侧侧滑 + private boolean isAllowEdgeRight; // 使用右侧侧滑 + + + public SlideLayout(Context context) { + this(context, null); + } + + public SlideLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.context = context; + this.mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + + // 获取屏幕信息,初始化控件设置 + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + float screenWidth = dm.widthPixels; + + backViewHeight = dm.heightPixels / 4f; // 高度默认 屏高/4 + arrowSize = dp2px(5); // 箭头大小默认 5dp + maxSlideLength = screenWidth / 12; // 最大宽度默认 屏宽/12 + + sideSlideLength = maxSlideLength / 2; // 侧滑响应距离默认 控件最大宽度/2 + + dragRate = 3; // 阻尼系数默认 3 + + // 侧滑返回模式 默认:左 + isAllowEdgeLeft = true; + isAllowEdgeRight = true; + + leftViewTriggerStart = sideSlideLength / 2; + leftViewTriggerEnd = maxSlideLength * 2; + rightViewTriggerStart = screenWidth - maxSlideLength * 2; + rightViewTriggerEnd = screenWidth - sideSlideLength / 2; + + mAnimation = ValueAnimator.ofFloat(0f, 1f); + mAnimation.setDuration(mDuration); + mAnimation.setInterpolator(new DecelerateInterpolator()); + mAnimUpdateListener = new AnimUpdateListener(this); + mAnimListenerAdapter = new AnimListenerAdapter(this); + } + + +// public SlideLayout setAllowEdgeLeft(boolean allowEdgeLeft) { +// isAllowEdgeLeft = allowEdgeLeft; +// slideBackIconViewLeft = new SlideBackIconView(context); +// slideBackIconViewLeft.setBackViewHeight(backViewHeight); +// slideBackIconViewLeft.setArrowSize(arrowSize); +// slideBackIconViewLeft.setArrowColor(arrowColor); +// slideBackIconViewLeft.setMaxSlideLength(maxSlideLength); +// addView(slideBackIconViewLeft); +// return this; +// } +// +// public SlideLayout setAllowEdgeRight(boolean allowEdgeRight) { +// isAllowEdgeRight = allowEdgeRight; +// slideBackIconViewRight = new SlideBackIconView(context); +// slideBackIconViewRight.setBackViewHeight(backViewHeight); +// slideBackIconViewRight.setArrowSize(arrowSize); +// slideBackIconViewRight.setArrowColor(arrowColor); +// slideBackIconViewRight.setMaxSlideLength(maxSlideLength); +// // 右侧侧滑 需要旋转180° +// slideBackIconViewRight.setRotationY(180); +// addView(slideBackIconViewRight); +// return this; +// } + + /** + * 回调 适用于新的左右模式 + */ + public SlideLayout callBack(SlideCallBack callBack) { + this.callBack = callBack; + return this; + } + + + /** + * 控件高度 默认屏高/4 + */ + public SlideLayout viewHeight(float backViewHeightDP) { + this.backViewHeight = dp2px(backViewHeightDP); + return this; + } + + /** + * 箭头大小 默认5dp + */ + public SlideLayout arrowSize(float arrowSizeDP) { + this.arrowSize = dp2px(arrowSizeDP); + return this; + } + + /** + * 箭头颜色 + */ + public SlideLayout arrowColor(@ColorInt int arrowColor) { + this.arrowColor = arrowColor; + return this; + } + + + /** + * 最大拉动距离(控件最大宽度) 默认屏宽/12 + */ + public SlideLayout maxSlideLength(float maxSlideLengthDP) { + this.maxSlideLength = dp2px(maxSlideLengthDP); + return this; + } + + /** + * 侧滑响应距离 默认控件最大宽度/2 + */ + public SlideLayout sideSlideLength(float sideSlideLengthDP) { + this.sideSlideLength = dp2px(sideSlideLengthDP); + return this; + } + + /** + * 阻尼系数 默认3(越小越灵敏) + */ + public SlideLayout dragRate(float dragRate) { + this.dragRate = dragRate; + return this; + } + + /** + * 边缘侧滑模式 默认左 + */ + public SlideLayout edgeMode(@SlideBack.EdgeMode int edgeMode) { + switch (edgeMode) { + case EDGE_LEFT: + isAllowEdgeLeft = true; + isAllowEdgeRight = false; + break; + case EDGE_RIGHT: + isAllowEdgeLeft = false; + isAllowEdgeRight = true; + break; + case EDGE_BOTH: + isAllowEdgeLeft = true; + isAllowEdgeRight = true; + break; + default: + throw new RuntimeException("未定义的边缘侧滑模式值:EdgeMode = " + edgeMode); + } + return this; + } + + + /** + * 需要使用滑动的页面注册 + */ + @SuppressLint("ClickableViewAccessibility") + public void register() { + if (isAllowEdgeLeft) { + // 初始化SlideBackIconView 左侧 + slideBackIconViewLeft = new SlideBackIconView(context); + slideBackIconViewLeft.setBackViewHeight(backViewHeight); + slideBackIconViewLeft.setArrowSize(arrowSize); + slideBackIconViewLeft.setArrowColor(arrowColor); + slideBackIconViewLeft.setMaxSlideLength(maxSlideLength); + addView(slideBackIconViewLeft); + } + if (isAllowEdgeRight) { + // 初始化SlideBackIconView - Right + slideBackIconViewRight = new SlideBackIconView(context); + slideBackIconViewRight.setBackViewHeight(backViewHeight); + slideBackIconViewRight.setArrowSize(arrowSize); + slideBackIconViewRight.setArrowColor(arrowColor); + slideBackIconViewRight.setMaxSlideLength(maxSlideLength); + // 右侧侧滑 需要旋转180° + slideBackIconViewRight.setRotationY(180); + addView(slideBackIconViewRight); + } + //KLog.e(" 是否要添加箭头:" + isAllowEdgeLeft + " , " + isAllowEdgeRight); + } + + + private boolean isSideSlideLeft = false; // 是否从左边边缘开始滑动 + private boolean isSideSlideRight = false; // 是否从右边边缘开始滑动 + private float moveXLength = 0; // 位移的X轴距离 + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: // 按下 + break; + case MotionEvent.ACTION_MOVE: // 移动 + //KLog.e("响应手势,移动:" + isSideSlideLeft + " , " + isSideSlideRight + " , " + isAllowEdgeLeft + " , " + isAllowEdgeRight); + if (isSideSlideLeft || isSideSlideRight) { + // 从边缘开始滑动 + // 获取X轴位移距离 + moveXLength = Math.abs(ev.getRawX() - mDownX); + //KLog.e("响应手势,移动B:" + moveXLength+ " , " + mDownX ); + if (moveXLength / dragRate <= maxSlideLength) { + // 如果位移距离在可拉动距离内,更新SlideBackIconView的当前拉动距离并重绘,区分左右 + if (isAllowEdgeLeft && isSideSlideLeft) { + slideBackIconViewLeft.updateSlideLength(moveXLength / dragRate); + //KLog.e("响应手势,移动B:" + slideBackIconViewLeft.getHeight()+ " , " + slideBackIconViewLeft.getVisibility() ); + callBack.onViewSlide(EDGE_LEFT, (int) (moveXLength / dragRate)); + } else if (isAllowEdgeRight && isSideSlideRight) { + slideBackIconViewRight.updateSlideLength(moveXLength / dragRate); + callBack.onViewSlide(EDGE_RIGHT, (int) (moveXLength / dragRate)); + } + } + + // 根据Y轴位置给SlideBackIconView定位 + if (isAllowEdgeLeft && isSideSlideLeft) { + setSlideBackPosition(slideBackIconViewLeft, (int) (ev.getY())); + } else if (isAllowEdgeRight && isSideSlideRight) { + setSlideBackPosition(slideBackIconViewRight, (int) (ev.getY())); + } + } + break; + case MotionEvent.ACTION_UP: // 抬起 + case MotionEvent.ACTION_CANCEL: + // 是从边缘开始滑动 且 抬起点的X轴坐标大于某值(默认3倍最大滑动长度) 且 回调不为空 + if ((isSideSlideLeft || isSideSlideRight) && moveXLength / dragRate >= maxSlideLength && null != callBack) { + // 区分左右 + callBack.onSlide(isSideSlideLeft ? EDGE_LEFT : EDGE_RIGHT); + } + + // 恢复SlideBackIconView的状态 + if (isAllowEdgeLeft && isSideSlideLeft) { + slideBackIconViewLeft.updateSlideLength(0); + callBack.onViewSlide(EDGE_LEFT, 0); + } else if (isAllowEdgeRight && isSideSlideRight) { + slideBackIconViewRight.updateSlideLength(0); + callBack.onViewSlide(EDGE_RIGHT, 0); + } + + // 从边缘开始滑动结束 + isSideSlideLeft = false; + isSideSlideRight = false; + break; + default: + break; + } + return isSideSlideLeft || isSideSlideRight; + } + + private float mDownX = 0; // 按下的X轴坐标 + private float mDownY = 0; // 按下的X轴坐标 + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: // 按下 + // 更新按下点的X轴坐标 + mDownX = ev.getRawX(); + mDownY = ev.getRawY(); + break; + case MotionEvent.ACTION_MOVE: + float offsetX = Math.abs(ev.getRawX() - mDownX); + float offsetY = Math.abs(ev.getRawY() - mDownY); + if (!(offsetX > mScaledTouchSlop * 2 && offsetY < mScaledTouchSlop)) { + break; + } + if (isAllowEdgeLeft && (mDownX >= leftViewTriggerStart && mDownX <= leftViewTriggerEnd)) { + isSideSlideLeft = true; + return true; + } else if (isAllowEdgeRight && (mDownX >= rightViewTriggerStart && mDownX <= rightViewTriggerEnd)) { + isSideSlideRight = true; + return true; + } + break; + } + return super.onInterceptTouchEvent(ev); + } + + /** + * 给SlideBackIconView设置topMargin,起到定位效果 + * + * @param view SlideBackIconView + * @param position 触点位置 + */ + private void setSlideBackPosition(SlideBackIconView view, int position) { + // 触点位置减去SlideBackIconView一半高度即为topMargin + int topMargin = (int) (position - (view.getBackViewHeight() / 2)); + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(view.getLayoutParams()); + layoutParams.topMargin = topMargin; + view.setLayoutParams(layoutParams); + } + + + private ValueAnimator mAnimation; + private AnimUpdateListener mAnimUpdateListener; + private AnimListenerAdapter mAnimListenerAdapter; + private float mFactor; // 进度因子:0-1 + private boolean mIsRunning; + private int mCurX, mCurY, mDst; + private int mDuration = 250; + private float mDampFactor = 0.6f; // 滑动阻尼系数 + + static class AnimListenerAdapter extends AnimatorListenerAdapter { + private final WeakReference reference; + + AnimListenerAdapter(SlideLayout view) { + this.reference = new WeakReference<>(view); + } + + @Override + public void onAnimationCancel(Animator animation) { + if (isFinish()) { + return; + } + SlideLayout view = reference.get(); + view.mFactor = 1; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (isFinish()) { + return; + } + SlideLayout view = reference.get(); + view.mFactor = 1; + } + + private boolean isFinish() { + SlideLayout view = reference.get(); + if (view == null || view.getContext() == null + || view.getContext() instanceof Activity && ((Activity) view.getContext()).isFinishing() + || !view.mIsRunning) { + return true; + } + return false; + } + } + + static class AnimUpdateListener implements ValueAnimator.AnimatorUpdateListener { + private final WeakReference reference; + + AnimUpdateListener(SlideLayout view) { + this.reference = new WeakReference<>(view); + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (isFinish()) { + return; + } + SlideLayout view = reference.get(); + view.mFactor = (float) animation.getAnimatedValue(); + if (view.mDst == -1) { + float scrollY = view.mCurY - view.mCurY * view.mFactor; + view.scrollTo(0, (int) scrollY); + } else if (view.mDst == 1) { + float scrollX = view.mCurX - view.mCurX * view.mFactor; + view.scrollTo((int) scrollX, 0); + } + view.invalidate(); + } + + private boolean isFinish() { + SlideLayout view = reference.get(); + if (view == null || view.getContext() == null + || view.getContext() instanceof Activity && ((Activity) view.getContext()).isFinishing() + || !view.mIsRunning) { + return true; + } + return false; + } + } + + +} diff --git a/app/src/main/java/me/wizos/loread/view/slideback/callback/SlideBackCallBack.java b/app/src/main/java/me/wizos/loread/view/slideback/callback/SlideBackCallBack.java new file mode 100644 index 0000000..5e2b70c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/callback/SlideBackCallBack.java @@ -0,0 +1,6 @@ +package me.wizos.loread.view.slideback.callback; + +public interface SlideBackCallBack { + void onSlideBack(); +// void onViewSlideUpdate(int offset); +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/slideback/callback/SlideCallBack.java b/app/src/main/java/me/wizos/loread/view/slideback/callback/SlideCallBack.java new file mode 100644 index 0000000..de3016e --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/callback/SlideCallBack.java @@ -0,0 +1,34 @@ +package me.wizos.loread.view.slideback.callback; + +public abstract class SlideCallBack implements SlideBackCallBack { + private SlideBackCallBack callBack; + + public SlideCallBack() { + } + + public SlideCallBack(SlideBackCallBack callBack) { + this.callBack = callBack; + } + + @Override + public void onSlideBack() { + if (null != callBack) { + callBack.onSlideBack(); + } + } +// @Override +// public void onViewSlideUpdate(int offset) { +// if (null != callBack) { +// callBack.onViewSlideUpdate(offset); +// } +// } + + /** + * 滑动来源:
          + * EDGE_LEFT 左侧侧滑
          + * EDGE_RIGHT 右侧侧滑
          + */ + public abstract void onSlide(int edgeFrom); + + public abstract void onViewSlide(int edgeFrom, int offset); +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/slideback/widget/SlideBackIconView.java b/app/src/main/java/me/wizos/loread/view/slideback/widget/SlideBackIconView.java new file mode 100644 index 0000000..d248e54 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/widget/SlideBackIconView.java @@ -0,0 +1,168 @@ +package me.wizos.loread.view.slideback.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; + +/** + * author : ParfoisMeng + * time : 2018/12/19 + * desc : 边缘返回的图标View + */ +public class SlideBackIconView extends View { + private Path arrowPath; // 路径对象 + private Paint arrowPaint; // 画笔对象 + //private Path bgPath; + //private Paint bgPaint; + + // @ColorInt + // private int backViewColor = Color.BLACK; // 控件背景色 + + private float backViewHeight = 0; // 控件高度 + private float arrowSize = 10; // 箭头图标大小 + @ColorInt + private int arrowColor = Color.WHITE; // 箭头背景色 + private float maxSlideLength = 0; // 最大拉动距离 + + private float slideLength = 0; // 当前拉动距离 + + public SlideBackIconView(Context context) { + this(context, null); + } + + public SlideBackIconView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SlideBackIconView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + /** + * 初始化 路径与画笔 + * Path & Paint + */ + private void init() { +// bgPath = new Path(); +// bgPaint = new Paint(); +// bgPaint.setAntiAlias(true); +// bgPaint.setStyle(Paint.Style.FILL_AND_STROKE); // 填充内部和描边 +// bgPaint.setColor(backViewColor); +// bgPaint.setStrokeWidth(1); // 画笔宽度 + + arrowPath = new Path(); + arrowPaint = new Paint(); + arrowPaint.setAntiAlias(true); + arrowPaint.setStyle(Paint.Style.STROKE); // 描边 + arrowPaint.setColor(arrowColor); + arrowPaint.setStrokeWidth(8); // 画笔宽度 + arrowPaint.setStrokeJoin(Paint.Join.ROUND); // * 结合处的样子 ROUND:圆弧 + + setAlpha(0); + } + + + /** + * 因为过程中会多次绘制,所以要先重置路径再绘制。 + * 贝塞尔曲线没什么好说的,相关文章有很多。此曲线经我测试比较类似“即刻App”。 + *

          + * 方便阅读再写一遍,此段代码中的变量定义: + * backViewHeight 控件高度 + * slideLength 当前拉动距离 + * maxSlideLength 最大拉动距离 + * arrowSize 箭头图标大小 + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + // 背景 +// bgPath.reset(); // 会多次绘制,所以先重置 +// bgPath.moveTo(0, 0); +// bgPath.cubicTo(0, backViewHeight * 2 / 9, slideLength, backViewHeight / 3, slideLength, backViewHeight / 2); +// bgPath.cubicTo(slideLength, backViewHeight * 2 / 3, 0, backViewHeight * 7 / 9, 0, backViewHeight); +// canvas.drawPath(bgPath, bgPaint); // 根据设置的贝塞尔曲线路径用画笔绘制 + + // 箭头是先直线由短变长再折弯变成箭头状的 + // 依据当前拉动距离和最大拉动距离计算箭头大小值 + // 大小到一定值后开始折弯,计算箭头角度值 + float arrowZoom = slideLength / maxSlideLength; // 箭头大小变化率 + float arrowAngle = arrowZoom < 0.75f ? 0 : (arrowZoom - 0.75f) * 2; // 箭头角度变化率 + // 箭头 + arrowPath.reset(); // 先重置 + // 结合箭头大小值与箭头角度值设置折线路径 + arrowPath.moveTo(slideLength / 2 + (arrowSize * arrowAngle), backViewHeight / 2 - (arrowZoom * arrowSize)); + arrowPath.lineTo(slideLength / 2 - (arrowSize * arrowAngle), backViewHeight / 2); + arrowPath.lineTo(slideLength / 2 + (arrowSize * arrowAngle), backViewHeight / 2 + (arrowZoom * arrowSize)); + canvas.drawPath(arrowPath, arrowPaint); + + setAlpha(slideLength / maxSlideLength - 0.2f); // 最多0.8透明度 + } + + /** + * 更新当前拉动距离并重绘 + * + * @param slideLength 当前拉动距离 + */ + public void updateSlideLength(float slideLength) { + this.slideLength = slideLength; + invalidate(); // 会再次调用onDraw + } + + + /** + * 设置最大拉动距离 + * + * @param maxSlideLength px值 + */ + public void setMaxSlideLength(float maxSlideLength) { + this.maxSlideLength = maxSlideLength; + } + + /** + * 设置箭头图标大小 + * + * @param arrowSize px值 + */ + public void setArrowSize(float arrowSize) { + this.arrowSize = arrowSize; + } + + /** + * 箭头颜色 + */ + public void setArrowColor(@ColorInt int arrowColor) { + if (arrowPaint != null) { + arrowPaint.setColor(arrowColor); + } + } + +// /** +// * 设置返回Icon背景色 +// * +// * @param backViewColor ColorInt +// */ +// public void setBackViewColor(@ColorInt int backViewColor) { +// this.backViewColor = backViewColor; +// } + + /** + * 设置返回Icon的高度 + * + * @param backViewHeight px值 + */ + public void setBackViewHeight(float backViewHeight) { + this.backViewHeight = backViewHeight; + } + + public float getBackViewHeight() { + return backViewHeight; + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/slideback/widget/SlideBackInterceptLayout.java b/app/src/main/java/me/wizos/loread/view/slideback/widget/SlideBackInterceptLayout.java new file mode 100644 index 0000000..773e47c --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/slideback/widget/SlideBackInterceptLayout.java @@ -0,0 +1,91 @@ +package me.wizos.loread.view.slideback.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +import com.socks.library.KLog; + +import java.util.ArrayList; + +/** + * author : ParfoisMeng + * time : 2019/01/10 + * desc : 处理事件拦截的Layout + */ +public class SlideBackInterceptLayout extends FrameLayout { + + private float leftSideSlideLength = 0; // 边缘滑动响应距离 + private float rightSideSlideLength = 0; // 边缘滑动响应距离 + + public SlideBackInterceptLayout(Context context) { + this(context, null); + } + + public SlideBackInterceptLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SlideBackInterceptLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + // @Override +// public boolean onInterceptTouchEvent(MotionEvent ev) { +// return ev.getAction() == MotionEvent.ACTION_DOWN && (ev.getRawX() <= leftSideSlideLength || ev.getRawX() >= rightSideSlideLength); +// } + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return ev.getAction() == MotionEvent.ACTION_DOWN && isMotionTrigger(ev); + } + + public void setSideSlideLength(float screenWidth, float sideSlideLength) { + this.leftSideSlideLength = sideSlideLength; + this.rightSideSlideLength = screenWidth - sideSlideLength; + } + + + private ArrayList zoneList; + + public void addXTriggerZone(float[] zone) { + if (zoneList == null) { + zoneList = new ArrayList<>(); + } + float tmp; + if (zone[0] > zone[1]) { + tmp = zone[0]; + zone[0] = zone[1]; + zone[1] = tmp; + zoneList.add(zone); + } else if (zone[0] < zone[1]) { + zoneList.add(zone); + } + } + + public void setXTriggerZone(float[]... zones) { + zoneList = new ArrayList<>(zones.length); + float tmp; + for (float[] zone : zones) { + if (zone[0] > zone[1]) { + tmp = zone[0]; + zone[0] = zone[1]; + zone[1] = tmp; + zoneList.add(zone); + } else if (zone[0] < zone[1]) { + zoneList.add(zone); + } + } + } + + private boolean isMotionTrigger(MotionEvent ev) { + for (float[] zone : zoneList) { + if (zone[0] <= ev.getRawX() && ev.getRawX() <= zone[1]) { + KLog.e("事件成功:" + zone[0] + " , " + ev.getRawX() + " , " + zone[1] + " = " + ev.getAction()); + return true; + } + } + KLog.e("事件不成功:" + " , " + ev.getRawX() + " , " + " = " + ev.getAction()); + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/me/wizos/loread/view/webview/DownloadListenerS.java b/app/src/main/java/me/wizos/loread/view/webview/DownloadListenerS.java index 117609d..d5d00f1 100644 --- a/app/src/main/java/me/wizos/loread/view/webview/DownloadListenerS.java +++ b/app/src/main/java/me/wizos/loread/view/webview/DownloadListenerS.java @@ -7,21 +7,23 @@ import android.content.Context; import android.net.Uri; import android.os.Environment; -import android.support.annotation.NonNull; import android.text.TextUtils; import android.webkit.DownloadListener; import android.webkit.WebView; import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; +import com.hjq.toast.ToastUtils; import com.socks.library.KLog; import me.wizos.loread.R; import me.wizos.loread.utils.FileUtil; -import me.wizos.loread.utils.ToastUtil; import me.wizos.loread.utils.Tool; +import me.wizos.loread.utils.UriUtil; import static android.content.Context.DOWNLOAD_SERVICE; @@ -46,44 +48,36 @@ public DownloadListenerS setWebView(WebView webView) { @Override public void onDownloadStart(final String url, final String userAgent, final String contentDisposition, final String mimeType, final long contentLength) { String neutralText = "复制下载地址"; - if (!TextUtils.isEmpty(mimeType)) { + if (!TextUtils.isEmpty(mimeType) && webView != null) { if (mimeType.toLowerCase().startsWith("video")) { neutralText = "播放该视频"; - } else if (mimeType.toLowerCase().startsWith("audio")) { + } else if (mimeType.toLowerCase().startsWith("audio")&& webView != null) { neutralText = "播放该音频"; } } - KLog.e("下载" + url); - KLog.e("下载", userAgent); - KLog.e("下载", contentDisposition); // attachment; filename=com.android36kr.app_7.4.2_18060821.apk + KLog.e("下载" + url + " , " + userAgent + " , " + contentDisposition); +// KLog.e("下载", contentDisposition); // attachment; filename=com.android36kr.app_7.4.2_18060821.apk KLog.e("下载" + mimeType); // application/vnd.android.package-archive KLog.e("下载5", contentLength); MaterialDialog downloadDialog = new MaterialDialog.Builder(context) - .title("是否下载文件?") + .title(R.string.do_you_want_to_download_files) .customView(R.layout.config_download_view, true) .neutralText(neutralText) .onNeutral(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { if (!TextUtils.isEmpty(mimeType)) { - if (mimeType.toLowerCase().startsWith("video")) { + if (mimeType.toLowerCase().startsWith("video")&& webView != null) { playVideo(url); - } else if (mimeType.toLowerCase().startsWith("audio")) { + } else if (mimeType.toLowerCase().startsWith("audio")&& webView != null) { playAudio(url); } else { copyUrl(url); } } -// if ("video/mp4".equals(mimeType)) { -// playVideo(url); -// } else if("audio/mp3".equals(mimeType)){ -// playAudio(url); -// }else { -// copyUrl(url); -// } } }) .negativeText(android.R.string.cancel) @@ -92,15 +86,12 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { // KLog.e("输入框内容的是:" + fileNameEditor.getText()); - downloadBySystem(url, fileNameEditor.getText() + ""); - if (!webView.canGoBack()) { - context.finish(); - context.overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - } + downloadBySystem(url, fileNameEditor.getText().toString()); } }) .show(); - String fileName = FileUtil.guessDownloadFileName(url, contentDisposition, mimeType); + + String fileName = UriUtil.guessFileName(url, contentDisposition, mimeType); String fileSize = Tool.getNetFileSizeDescription(context, contentLength); fileNameEditor = (EditText) downloadDialog.findViewById(R.id.file_name_edit); @@ -131,19 +122,19 @@ private void downloadBySystem(String url, String fileName) { // request.setTitle("This is title"); // 设置通知栏的描述 // request.setDescription("This is description"); + // 允许该记录在下载管理界面可见 + request.setVisibleInDownloadsUi(true); // 允许在计费流量下下载 request.setAllowedOverMetered(true); - // 允许该记录在下载管理界面可见 - request.setVisibleInDownloadsUi(false); // 允许漫游时下载 request.setAllowedOverRoaming(true); // 允许下载的网路类型 request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE); + // 设置下载文件保存的路径和文件名。 // Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。(请注意,这是设计导致的;无法使用此功能将文档保存到用户的计算机上,而不向用户询问保存位置。) - // KLog.e("下载", "文件名:" + fileName); - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, FileUtil.getSaveableName(fileName)); // 另外可选一下方法,自定义下载路径 // request.setDestinationUri() // request.setDestinationInExternalFilesDir() @@ -171,6 +162,7 @@ private void playAudio(String url) { "UTF-8", null); } + private void copyUrl(String url) { // 获取剪贴板管理器 ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); @@ -178,6 +170,6 @@ private void copyUrl(String url) { ClipData mClipData = ClipData.newPlainText("url", url); // 将ClipData内容放到系统剪贴板里。 cm.setPrimaryClip(mClipData); - ToastUtil.showLong("复制成功"); + ToastUtils.show("复制成功"); } } diff --git a/app/src/main/java/me/wizos/loread/view/webview/FastScrollWebView.java b/app/src/main/java/me/wizos/loread/view/webview/FastScrollWebView.java index 93ce1c7..bb1f07b 100644 --- a/app/src/main/java/me/wizos/loread/view/webview/FastScrollWebView.java +++ b/app/src/main/java/me/wizos/loread/view/webview/FastScrollWebView.java @@ -10,8 +10,8 @@ import android.view.View; import android.webkit.WebView; -import me.wizos.loread.utils.FastScrollDelegate; -import me.wizos.loread.utils.FastScrollDelegate.FastScrollable; +import me.wizos.loread.view.fastscroll.FastScrollDelegate; +import me.wizos.loread.view.fastscroll.FastScrollDelegate.FastScrollable; /** * https://github.com/Mixiaoxiao/FastScroll-Everywhere FastScrollWebView diff --git a/app/src/main/java/me/wizos/loread/view/webview/LongClickPopWindow.java b/app/src/main/java/me/wizos/loread/view/webview/LongClickPopWindow.java new file mode 100644 index 0000000..1cb5b98 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/view/webview/LongClickPopWindow.java @@ -0,0 +1,153 @@ +package me.wizos.loread.view.webview; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebView; +import android.widget.PopupWindow; + +import com.hjq.toast.ToastUtils; +import com.socks.library.KLog; + +import me.wizos.loread.R; + +/** + * @author Wizos on 2018/9/16. + */ + +public class LongClickPopWindow extends PopupWindow { + private View webViewLongClickedPopWindow; + private Context context; + private WebView.HitTestResult result; + private WebView webView; + private int x, y; + + /** + * 构造函数 + * + * @param context 上下文 + * @param width 宽度 + * @param height 高度 * + */ + public LongClickPopWindow(Context context, WebView webView, int width, int height, int x, int y) { + super(context); + if (context == null | webView == null) { + return; + } + this.result = webView.getHitTestResult(); + if (null == result) { + return; + } + if (result.getType() == WebView.HitTestResult.UNKNOWN_TYPE) { + KLog.e("长按未知:" + result.getType() + " , " + result.getExtra()); + return; + } + this.context = context; + this.webView = webView; + this.x = x; + this.y = y; + LayoutInflater itemLongClickedPopWindowInflater = LayoutInflater.from(this.context); + this.webViewLongClickedPopWindow = itemLongClickedPopWindowInflater.inflate(R.layout.webview_long_clicked_popwindow, null); + + //设置默认选项 + setWidth(width); + setHeight(height); + setContentView(this.webViewLongClickedPopWindow); + setOutsideTouchable(true); + setFocusable(true); + + //创建 + initTab(); +// showAtLocation(webView, Gravity.TOP|Gravity.LEFT, downX, downY + 10); + } + + //实例化 + private void initTab() { + + switch (result.getType()) { +// 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 WebView.HitTestResult.EDIT_TEXT_TYPE: // 选中的文字类型 +// case WebView.HitTestResult.PHONE_TYPE: // 处理拨号 +// case WebView.HitTestResult.EMAIL_TYPE: // 处理Email +// case WebView.HitTestResult.GEO_TYPE: //  地图类型 +// case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超链接 +// case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: // 带有链接的图片类型 +// case WebView.HitTestResult.IMAGE_TYPE: // 处理长按图片的菜单项 +//  String url = result.getExtra();//获取图片 +// break; +// case WebView.HitTestResult.UNKNOWN_TYPE: //未知 + + + case WebView.HitTestResult.SRC_ANCHOR_TYPE://超链接 + if(TextUtils.isEmpty(result.getExtra())){ + return; + } + KLog.d("超链接为:" + result.getExtra() ); + this.webViewLongClickedPopWindow.findViewById(R.id.webview_copy_link) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LongClickPopWindow.this.dismiss(); + //获取剪贴板管理器: + ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + // 创建普通字符型ClipData + ClipData mClipData = ClipData.newRawUri("url", Uri.parse(result.getExtra())); + // 将ClipData内容放到系统剪贴板里。 + cm.setPrimaryClip(mClipData); + ToastUtils.show(context.getString(R.string.copy_success)); + } + }); + + this.webViewLongClickedPopWindow.findViewById(R.id.webview_share_link) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LongClickPopWindow.this.dismiss(); + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.setType("text/plain"); + sendIntent.putExtra(Intent.EXTRA_TEXT, result.getExtra()); +// sendIntent.setData(Uri.parse(status.getExtra())); + //sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(Intent.createChooser(sendIntent, context.getString(R.string.share_to))); + } + }); + + this.webViewLongClickedPopWindow.findViewById(R.id.webview_open_mode) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LongClickPopWindow.this.dismiss(); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(result.getExtra())); + // 每次都要选择打开方式 + context.startActivity(Intent.createChooser(intent, context.getString(R.string.open_mode))); + //context.startActivity(intent); + } + }); + + showAtLocation(webView, Gravity.TOP | Gravity.START, x, y); + break; + case WebView.HitTestResult.IMAGE_TYPE: //图片 + // TODO: 2019/5/1 重新下载 , 查看原图 + break; + case WebView.HitTestResult.UNKNOWN_TYPE: //对于历史内容弹出菜单,未作处理 + break; + } + } + +} diff --git a/app/src/main/java/me/wizos/loread/view/webview/NestedScrollWebView.java b/app/src/main/java/me/wizos/loread/view/webview/NestedScrollWebView.java index 642b382..df59c04 100644 --- a/app/src/main/java/me/wizos/loread/view/webview/NestedScrollWebView.java +++ b/app/src/main/java/me/wizos/loread/view/webview/NestedScrollWebView.java @@ -16,11 +16,12 @@ package me.wizos.loread.view.webview; import android.content.Context; -import android.support.v4.view.NestedScrollingChild; -import android.support.v4.view.NestedScrollingChildHelper; -import android.support.v4.view.ViewCompat; import android.view.MotionEvent; +import androidx.core.view.NestedScrollingChild; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.ViewCompat; + //import com.tencent.smtt.sdk.WebView; /** @@ -71,27 +72,6 @@ private void initView() { boolean mIsBeingDragged; -// case MotionEvent.ACTION_DOWN: -// mPrevX = MotionEvent.obtain(ev).getX(); -// break; -// case MotionEvent.ACTION_MOVE: -// final float eventX = ev.getX(); -// //获取水平移动距离 -// float xDiff = Math.abs(eventX - mPrevX); -// KLog.e("是否拦截事件:" + (xDiff>mTouchSlop)); -// //当水平移动距离大于滑动操作的最小距离的时候就认为进行了横向滑动,不进行事件拦截,并将这个事件交给子View处理 -// if (xDiff > mTouchSlop) { -// return false; -// } - -// private int downY; -// private boolean onlyHorizontalMove(MotionEvent ev) { -// return (Math.abs(ev.getX() - mLastMotionY) > mTouchSlop && Math.abs(ev.getY() - downY) < mTouchSlop); -// } -// private boolean onlyVerticalMove(MotionEvent ev) { -// return (Math.abs(ev.getX() - mLastMotionY) < mTouchSlop && Math.abs(ev.getY() - downY) > mTouchSlop); -// } - @Override public boolean onTouchEvent(MotionEvent ev) { final int actionMasked = ev.getActionMasked(); @@ -158,36 +138,6 @@ public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev); } - -// private int mTouchSlop; -// private float mPrevX; - -// /** -// * 作者:秋天的雨滴 -// * 链接:https://www.jianshu.com/p/04d799608c2e -// * 解决该view上下滑动事件与子view左右滑动事件的冲突问题 -// */ -// @Override -// public boolean onInterceptTouchEvent(MotionEvent ev) { -// switch (ev.getAction()) { -// case MotionEvent.ACTION_DOWN: -// mPrevX = MotionEvent.obtain(ev).getX(); -// break; -// case MotionEvent.ACTION_MOVE: -// final float eventX = ev.getX(); -// //获取水平移动距离 -// float xDiff = Math.abs(eventX - mPrevX); -// KLog.e("是否拦截事件:" + (xDiff>mTouchSlop)); -// //当水平移动距离大于滑动操作的最小距离的时候就认为进行了横向滑动,不进行事件拦截,并将这个事件交给子View处理 -// if (xDiff > mTouchSlop) { -// return false; -// } -// default: -// break; -// } -// return super.onInterceptTouchEvent(ev); -// } - private void endDrag() { mIsBeingDragged = false; stopNestedScroll(); diff --git a/app/src/main/java/me/wizos/loread/view/webview/SlowlyProgressBar.java b/app/src/main/java/me/wizos/loread/view/webview/SlowlyProgressBar.java index 66a5136..cbf2780 100644 --- a/app/src/main/java/me/wizos/loread/view/webview/SlowlyProgressBar.java +++ b/app/src/main/java/me/wizos/loread/view/webview/SlowlyProgressBar.java @@ -5,12 +5,15 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.view.View; +import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.ProgressBar; +import com.socks.library.KLog; + /** * @author 林冠宏 on 2016/7/11. Wizos on 2018/3/18 - * https://github.com/af913337456/SlowlyProgressBar + * https://github.com/af913337456/SlowlyProgressBar */ public class SlowlyProgressBar { @@ -21,6 +24,20 @@ public SlowlyProgressBar(ProgressBar progressBar) { this.progressBar = progressBar; } + public void destroy() { + try { + // removeAllViewsInLayout(); 相比而言, removeAllViews() 也调用了removeAllViewsInLayout(), 但是后面还调用了requestLayout(),这个方法是当View的布局发生改变会调用它来更新当前视图, 移除子View会更加彻底. 所以除非必要, 还是推荐使用removeAllViews()这个方法. + ViewGroup parent = (ViewGroup) progressBar.getParent(); + if (parent != null) { + parent.removeView(progressBar); + } + progressBar = null; + } catch (Exception e) { + KLog.e("报错"); + e.printStackTrace(); + } + } + /** * 在 WebViewClient onPageStarted 调用 */ diff --git a/app/src/main/java/me/wizos/loread/view/webview/VideoImpl.java b/app/src/main/java/me/wizos/loread/view/webview/VideoImpl.java index 4e6e05e..fd77822 100644 --- a/app/src/main/java/me/wizos/loread/view/webview/VideoImpl.java +++ b/app/src/main/java/me/wizos/loread/view/webview/VideoImpl.java @@ -19,8 +19,6 @@ import android.app.Activity; import android.content.pm.ActivityInfo; import android.graphics.Color; -import android.os.Build; -import android.support.v4.util.Pair; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -29,10 +27,14 @@ import android.webkit.WebView; import android.widget.FrameLayout; +import androidx.core.util.Pair; + import java.util.HashSet; import java.util.Set; /** + * https://www.jianshu.com/p/ed01d00809f4 + * * @author cenxiaozhong */ public class VideoImpl { // implements IVideo, EventInterceptor @@ -72,7 +74,8 @@ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callb mFlags.add(mPair); } - if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && (mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) == 0) { + // 开启Window级别的硬件加速 + if ((mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) == 0) { mPair = new Pair<>(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 0); mWindow.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); mFlags.add(mPair); diff --git a/app/src/main/java/me/wizos/loread/viewmodel/ArticleViewModel.java b/app/src/main/java/me/wizos/loread/viewmodel/ArticleViewModel.java new file mode 100644 index 0000000..3119132 --- /dev/null +++ b/app/src/main/java/me/wizos/loread/viewmodel/ArticleViewModel.java @@ -0,0 +1,360 @@ +package me.wizos.loread.viewmodel; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; +import androidx.paging.DataSource; +import androidx.paging.LivePagedListBuilder; +import androidx.paging.PagedList; + +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.db.Article; +import me.wizos.loread.db.ArticleDao; +import me.wizos.loread.db.CoreDB; + +//LiveData通常结合ViewModel一起使用。我们知道ViewModel是用来存放数据的,因此我们可以将数据库放在ViewModel中进行实例化。 +// 但数据库在实例化的时候需要Context,而ViewModel不能传入任何带有Context引用的对象,所以应该用它的子类AndroidViewModel,它可以接受Application作为参数,用于数据库的实例化。 +public class ArticleViewModel extends ViewModel { + public LiveData> articles; + public LiveData> getArticles(String uid, String streamId, int streamType, int streamStatus){ + //KLog.i("生成 getArticles :" + streamId ); + ArticleDao articleDao = CoreDB.i().articleDao(); + long timeMillis = System.currentTimeMillis(); + DataSource.Factory articleFactory = null; + + if (streamType == App.TYPE_GROUP ) { + if (streamId.contains(App.CATEGORY_ALL)) { + if (streamStatus == App.STATUS_STARED) { + articleFactory = articleDao.getStared(uid, timeMillis); + } else if (streamStatus == App.STATUS_UNREAD) { + articleFactory = articleDao.getUnread(uid, timeMillis); + } else { + articleFactory = articleDao.getAll(uid, timeMillis); + } + } else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { + if (streamStatus == App.STATUS_STARED) { + articleFactory = articleDao.getStaredByUncategory(uid, timeMillis); + } else if (streamStatus == App.STATUS_UNREAD) { + articleFactory = articleDao.getUnreadByUncategory(uid, timeMillis); + } else { + articleFactory = articleDao.getAllByUncategory(uid, timeMillis); + } + } else { + KLog.e("获取到的分类:" + streamId ); + if (streamStatus == App.STATUS_STARED) { + articleFactory = articleDao.getStaredByCategoryId(uid, streamId, timeMillis); + } else if (streamStatus == App.STATUS_UNREAD) { + articleFactory = articleDao.getUnreadByCategoryId(uid, streamId, timeMillis); + } else { + articleFactory = articleDao.getAllByCategoryId(uid, streamId, timeMillis); + } + } + } else if (streamType == App.TYPE_FEED ) { + if (streamStatus == App.STATUS_STARED) { + articleFactory = articleDao.getStaredByFeedId(uid, streamId, timeMillis); + } else if (streamStatus == App.STATUS_UNREAD) { + articleFactory = articleDao.getUnreadByFeedId(uid, streamId, timeMillis); + } else { + articleFactory = articleDao.getAllByFeedId(uid, streamId, timeMillis); + } + } + // setPageSize 指定每次分页加载的条目数量 + assert articleFactory != null; + articles = new LivePagedListBuilder<>(articleFactory, new PagedList.Config.Builder() + .setInitialLoadSizeHint(30) + .setPageSize(30) + .setPrefetchDistance(15) + .build() + ).build(); + return articles; + } + + +// public LiveData> getArticles2(String uid, String streamId, int streamType, int streamStatus){ +// //KLog.i("生成 getArticles :" + streamId ); +// ArticleDao articleDao = CoreDB.i().articleDao(); +// long timeMillis = System.currentTimeMillis(); +// DataSource.Factory articleFactory = null; +// if(streamStatus == App.STATUS_STARED){ +// if(streamType == App.TYPE_GROUP){ +// if (streamId.contains(App.CATEGORY_ALL)) { +// articleFactory = articleDao.getStared(uid, timeMillis); +// }else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// articleFactory = articleDao.getStaredByUnTag(uid, timeMillis); +// }else { +// KLog.e("加载列表:" + streamId + " , " + timeMillis); +// articleFactory = articleDao.getStaredByTagId(uid, streamId, timeMillis); +// } +// }else { +// articleFactory = articleDao.getStaredByFeedId(uid, streamId, timeMillis); +// } +// }else if (streamStatus == App.STATUS_UNREAD) { +// if(streamType == App.TYPE_GROUP){ +// if (streamId.contains(App.CATEGORY_ALL)) { +// articleFactory = articleDao.getUnread(uid, timeMillis); +// }else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// articleFactory = articleDao.getUnreadByUncategory(uid, timeMillis); +// }else { +// articleFactory = articleDao.getUnreadByCategoryId(uid, streamId, timeMillis); +// } +// }else { +// articleFactory = articleDao.getUnreadByFeedId(uid, streamId, timeMillis); +// } +// }else { +// if(streamType == App.TYPE_GROUP){ +// if (streamId.contains(App.CATEGORY_ALL)) { +// articleFactory = articleDao.getAll(uid, timeMillis); +// }else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// articleFactory = articleDao.getAllByUncategory(uid, timeMillis); +// }else { +// articleFactory = articleDao.getAllByCategoryId(uid, streamId, timeMillis); +// } +// }else { +// articleFactory = articleDao.getAllByFeedId(uid, streamId, timeMillis); +// } +// } +// // setPageSize 指定每次分页加载的条目数量 +// assert articleFactory != null; +// articles = new LivePagedListBuilder<>(articleFactory, new PagedList.Config.Builder() +// .setInitialLoadSizeHint(30) +// .setPageSize(30) +// .setPrefetchDistance(15) +// .build() +// ).build(); +// +// KLog.e("加载列表B:" + articles.getValue()); +// if( articles.getValue() != null){ +// KLog.e("加载列表C:" + articles.getValue().size()); +// } +// return articles; +// } + + public LiveData> getAllByKeyword(String uid, String keyword){ + // setPageSize 指定每次分页加载的条目数量 + articles = new LivePagedListBuilder<>(CoreDB.i().articleDao().getAllByKeyword(uid,"%" + keyword + "%"), new PagedList.Config.Builder().setPageSize(30).setInitialLoadSizeHint(30).setPrefetchDistance(15).build()).build(); + return articles; + } + +// public Article getItem(int position){ +// return articles.getValue().get(position); +//// return listData.get(position); +// } +// public int getItemCount(){ +// return articles.getValue().size(); +//// return listData.size(); +// } + +// public void updateItem(Article article){ +// listData.update(article); +// } + +// private ArticleLazyList listData; +// public List

          getListData(String uid, String streamId, int streamType, int streamStatus){ +// KLog.e("生成ArticleViewModel 2 :" + streamId ); +// ArticleDao articleDao = CoreDB.i().articleDao(); +// if (streamId.startsWith("user/") || streamType == App.TYPE_GROUP ) { +// if (streamId.contains(App.CATEGORY_ALL)) { +// if (streamStatus == App.STATUS_STARED) { +// listData = articleDao.getStared(uid); +// } else if (streamStatus == App.STATUS_UNREAD) { +// listData = articleDao.getUnread(uid); +// } else { +// listData = articleDao.getAll(uid); +// } +// } else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// if (streamStatus == App.STATUS_STARED) { +// listData = articleDao.getStaredByUncategory(uid); +// } else if (streamStatus == App.STATUS_UNREAD) { +// listData = articleDao.getUnreadByUncategory(uid); +// } else { +// listData = articleDao.getAllByUncategory(uid); +// } +// } else { +// // TEST: 测试 +// //Category theCategory = WithDB.i().getCategoryById(streamId); +// KLog.e("获取到的分类:" + streamId ); +// if (streamStatus == App.STATUS_STARED) { +// listData = articleDao.getStaredByCategoryId(uid, streamId); +// } else if (streamStatus == App.STATUS_UNREAD) { +// listData = articleDao.getUnreadByCategoryId(uid, streamId); +// } else { +// listData = articleDao.getAllByCategoryId(uid, streamId); +// } +// } +// } else if (streamId.startsWith("feed/") || streamType == App.TYPE_FEED ) { +// if (streamStatus == App.STATUS_STARED) { +// listData = articleDao.getStaredByFeedId(uid, streamId); +// } else if (streamStatus == App.STATUS_UNREAD) { +// listData = articleDao.getUnreadByFeedId(uid, streamId); +// } else { +// listData = articleDao.getAllByFeedId(uid, streamId); +// } +// } +// // setPageSize 指定每次分页加载的条目数量 +// return listData; +// } + + +// public List
          getListData2(String uid, String streamId, int streamType, int streamStatus){ +// ArticleDao articleDao = CoreDB.i().articleDao(); +// boolean includeValueless = App.i().getGlobalKV().getBoolean("including_valueless",false); +// KLog.e("获取到的分类:" + streamId ); +// if (streamId.startsWith("user/") || streamType == App.TYPE_GROUP ) { +// if (streamId.contains(App.CATEGORY_ALL)) { +// if (includeValueless) { +// listData = articleDao.getAll(uid); +// } else { +// listData = articleDao.getValuable(uid); +// } +// } else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// if (includeValueless) { +// listData = articleDao.getAllByUncategory(uid); +// } else { +// listData = articleDao.getValuableByUnCategory(uid); +// } +// } else { +// if (includeValueless) { +// listData = articleDao.getAllByCategoryId(uid, streamId); +// } else { +// listData = articleDao.getValuableByCategoryId(uid, streamId); +// } +// } +// } else if (streamId.startsWith("feed/") || streamType == App.TYPE_FEED ) { +// if (includeValueless) { +// listData = articleDao.getAllByFeedId(uid, streamId); +// } else { +// listData = articleDao.getValuableByFeedId(uid, streamId); +// } +// } +// // setPageSize 指定每次分页加载的条目数量 +// return listData; +// } + +// public List
          getListData3(String uid, String streamId, int streamType, int streamStatus){ +// ArticleDao articleDao = CoreDB.i().articleDao(); +// Cursor cursor = null; +// if (streamId.startsWith("user/") || streamType == App.TYPE_GROUP ) { +// if (streamId.contains(App.CATEGORY_ALL)) { +// if (streamStatus == App.STATUS_STARED) { +// cursor = articleDao.getStared(uid); +// } else if (streamStatus == App.STATUS_UNREAD) { +// cursor = articleDao.getUnread(uid); +// } else { +// cursor = articleDao.getAll(uid); +// } +// } else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// if (streamStatus == App.STATUS_STARED) { +// cursor = articleDao.getStaredByUncategory(uid); +// } else if (streamStatus == App.STATUS_UNREAD) { +// cursor = articleDao.getUnreadByUncategory(uid); +// } else { +// cursor = articleDao.getAllByUncategory(uid); +// } +// } else { +// // TEST: 测试 +// //Category theCategory = WithDB.i().getCategoryById(streamId); +// KLog.e("获取到的分类:" + streamId ); +// if (streamStatus == App.STATUS_STARED) { +// cursor = articleDao.getStaredByCategoryId(uid, streamId); +// } else if (streamStatus == App.STATUS_UNREAD) { +// cursor = articleDao.getUnreadByCategoryId(uid, streamId); +// } else { +// cursor = articleDao.getAllByCategoryId(uid, streamId); +// } +// } +// } else if (streamId.startsWith("feed/") || streamType == App.TYPE_FEED ) { +// if (streamStatus == App.STATUS_STARED) { +// cursor = articleDao.getStaredByFeedId(uid, streamId); +// } else if (streamStatus == App.STATUS_UNREAD) { +// cursor = articleDao.getUnreadByFeedId(uid, streamId); +// } else { +// cursor = articleDao.getAllByFeedId(uid, streamId); +// } +// } +// if(listData != null){ +// ((ArticleLazyList)listData).close(); +// } +// listData = new ArticleLazyList(cursor,true); +// AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { +// @Override +// public void run() { +// ((ArticleLazyList)listData).loadRemaining(); +// } +// }); +// return listData; +// } +// +// public List
          getAllByKeyword(String uid, String keyword){ +// if(listData != null){ +// ((ArticleLazyList)listData).close(); +// } +// listData = new ArticleLazyList(CoreDB.i().articleDao().getAllByKeyword(uid,"%" + keyword + "%"),true); +// AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { +// @Override +// public void run() { +// ((ArticleLazyList)listData).loadRemaining(); +// } +// }); +// return listData; +// } + + +// public LiveData> getArticles2(String uid, String streamId, int streamType, int streamStatus){ +// final long timeMillis = System.currentTimeMillis(); +// KLog.e("获取文章时间为:" + timeMillis ); +// articles = new LivePagedListBuilder<>(CoreDB.i().articleDao().getUnread(uid,timeMillis), new PagedList.Config.Builder().setPageSize(10).setInitialLoadSizeHint(10).setPrefetchDistance(5).build()).build(); +// return articles; +// } + +// public LiveData> getArticles(){ +// return articles; +// } +// public void queryArticles(String uid, String streamId, int streamType, int streamStatus){ +// ArticleDao articleDao = CoreDB.i().articleDao(); +// final long timeMillis = System.currentTimeMillis(); +// DataSource.Factory articleFactory = null; +// +// if (streamId.startsWith("user/") || streamType == App.TYPE_GROUP ) { +// if (streamId.contains(App.CATEGORY_ALL)) { +// if (streamStatus == App.STATUS_STARED) { +// articleFactory = articleDao.getStared(uid, timeMillis); +// } else if (streamStatus == App.STATUS_UNREAD) { +// articleFactory = articleDao.getUnread(uid, timeMillis); +// } else { +// articleFactory = articleDao.getAll(uid); +// } +// } else if (streamId.contains(App.CATEGORY_UNCATEGORIZED)) { +// if (streamStatus == App.STATUS_STARED) { +// articleFactory = articleDao.getStaredByUncategory(uid, timeMillis); +// } else if (streamStatus == App.STATUS_UNREAD) { +// articleFactory = articleDao.getUnreadByUncategory(uid, timeMillis); +// } else { +// articleFactory = articleDao.getAllByUncategory(uid); +// } +// } else { +// // TEST: 测试 +// //Category theCategory = WithDB.i().getCategoryById(streamId); +// KLog.e("获取到的分类:" + streamId ); +// if (streamStatus == App.STATUS_STARED) { +// articleFactory = articleDao.getStaredByCategoryId(uid, streamId, timeMillis); +// } else if (streamStatus == App.STATUS_UNREAD) { +// articleFactory = articleDao.getUnreadByCategoryId(uid, streamId, timeMillis); +// } else { +// articleFactory = articleDao.getAllByCategoryId(uid, streamId); +// } +// } +// } else if (streamId.startsWith("feed/") || streamType == App.TYPE_FEED ) { +// if (streamStatus == App.STATUS_STARED) { +// articleFactory = articleDao.getStaredByFeedId(uid, streamId, timeMillis); +// } else if (streamStatus == App.STATUS_UNREAD) { +// articleFactory = articleDao.getUnreadByFeedId(uid, streamId, timeMillis); +// } else { +// articleFactory = articleDao.getAllByFeedId(uid, streamId); +// } +// } +// // setPageSize 指定每次分页加载的条目数量 +// articles = new LivePagedListBuilder<>(articleFactory, new PagedList.Config.Builder().setPageSize(30).setInitialLoadSizeHint(10).setPrefetchDistance(5).build()).build(); +// } + +} diff --git a/app/src/main/java/me/wizos/loread/viewmodel/InoReaderUserViewModel.java b/app/src/main/java/me/wizos/loread/viewmodel/InoReaderUserViewModel.java new file mode 100644 index 0000000..b39fdfc --- /dev/null +++ b/app/src/main/java/me/wizos/loread/viewmodel/InoReaderUserViewModel.java @@ -0,0 +1,115 @@ +package me.wizos.loread.viewmodel; + +import android.app.Application; +import android.text.TextUtils; +import android.util.Patterns; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.Contract; +import me.wizos.loread.R; +import me.wizos.loread.activity.login.LoginFormState; +import me.wizos.loread.activity.login.LoginResult; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.db.UserDao; +import me.wizos.loread.network.api.InoReaderApi; +import me.wizos.loread.network.callback.CallbackX; + +// LiveData通常结合ViewModel一起使用。我们知道ViewModel是用来存放数据的,因此我们可以将数据库放在ViewModel中进行实例化。 +// 但数据库在实例化的时候需要Context,而ViewModel不能传入任何带有Context引用的对象,所以应该用它的子类AndroidViewModel,它可以接受Application作为参数,用于数据库的实例化。 +public class InoReaderUserViewModel extends AndroidViewModel { + private UserDao userDao; + // Creates a PagedList object with 50 items per page. + public InoReaderUserViewModel(@NonNull Application application) { + super(application); + this.userDao = CoreDB.i().userDao(); + } + + + private MutableLiveData loginFormLiveData = new MutableLiveData<>(); + private MutableLiveData loginResultLiveData = new MutableLiveData<>(); + + public LiveData getLoginFormLiveData() { + return loginFormLiveData; + } + + public LiveData getLoginResult() { + return loginResultLiveData; + } + + public void login(String host, String username, String password) { + InoReaderApi inoReaderApi = new InoReaderApi(host); + + inoReaderApi.login(username, password, new CallbackX() { + @Override + public void onSuccess(String auth) { + User user = new User(); + user.setSource(Contract.PROVIDER_INOREADER); + user.setId(Contract.PROVIDER_INOREADER + "_" + username); + user.setUserId(username); + user.setUserName(username); + user.setUserPassword(password); + user.setAuth(auth); + user.setExpiresTimestamp(0); + user.setHost(host); + inoReaderApi.setAuthorization(auth); + App.i().getKeyValue().putString(Contract.UID, user.getId()); + KLog.i("登录成功:" + user.getId()); + User userTmp = userDao.getById(user.getId()); + if (userTmp != null) { + CoreDB.i().userDao().update(user); + }else { + CoreDB.i().userDao().insert(user); + } + + LoginResult loginResult = new LoginResult().setSuccess(true).setData(auth); + loginResultLiveData.postValue(loginResult); + } + + @Override + public void onFailure(String error) { + LoginResult loginResult = new LoginResult().setSuccess(false).setData(App.i().getString(R.string.login_failed_reason, error)); + loginResultLiveData.postValue(loginResult); + } + }); + } + + public void loginDataChanged(String host, String username, String password) { + LoginFormState loginFormState = new LoginFormState(); + + if (!isHostValid(host)) { + loginFormState.setHostHint(R.string.invalid_host); + } else if (!isUserNameValid(username)) { + loginFormState.setUsernameHint(R.string.invalid_username); + } else if (!isPasswordValid(password)) { + loginFormState.setPasswordHint(R.string.invalid_password); + } else { + loginFormState.setDataValid(true); + } + + loginFormLiveData.setValue(loginFormState); + } + + private boolean isHostValid(String host) { + return !TextUtils.isEmpty(host) && Patterns.WEB_URL.matcher(host).matches(); + } + + // A placeholder username validation check + private boolean isUserNameValid(String username) { + return !TextUtils.isEmpty(username) && !username.trim().isEmpty(); + } + + // A placeholder password validation check + private boolean isPasswordValid(String password) { + return !TextUtils.isEmpty(password) && password.trim().length() > 5; + } + + +} diff --git a/app/src/main/java/me/wizos/loread/viewmodel/TinyRSSUserViewModel.java b/app/src/main/java/me/wizos/loread/viewmodel/TinyRSSUserViewModel.java new file mode 100644 index 0000000..2dc236e --- /dev/null +++ b/app/src/main/java/me/wizos/loread/viewmodel/TinyRSSUserViewModel.java @@ -0,0 +1,113 @@ +package me.wizos.loread.viewmodel; + +import android.app.Application; +import android.text.TextUtils; +import android.util.Patterns; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.socks.library.KLog; + +import me.wizos.loread.App; +import me.wizos.loread.Contract; +import me.wizos.loread.R; +import me.wizos.loread.activity.login.LoginFormState; +import me.wizos.loread.activity.login.LoginResult; +import me.wizos.loread.db.CoreDB; +import me.wizos.loread.db.User; +import me.wizos.loread.db.UserDao; +import me.wizos.loread.network.api.TinyRSSApi; +import me.wizos.loread.network.callback.CallbackX; + +// LiveData通常结合ViewModel一起使用。我们知道ViewModel是用来存放数据的,因此我们可以将数据库放在ViewModel中进行实例化。 +// 但数据库在实例化的时候需要Context,而ViewModel不能传入任何带有Context引用的对象,所以应该用它的子类AndroidViewModel,它可以接受Application作为参数,用于数据库的实例化。 +public class TinyRSSUserViewModel extends AndroidViewModel { + private UserDao userDao; + // Creates a PagedList object with 50 items per page. + public TinyRSSUserViewModel(@NonNull Application application) { + super(application); + this.userDao = CoreDB.i().userDao(); + } + + + private MutableLiveData loginFormLiveData = new MutableLiveData<>(); + private MutableLiveData loginResultLiveData = new MutableLiveData<>(); + + public LiveData getLoginFormLiveData() { + return loginFormLiveData; + } + + public LiveData getLoginResult() { + return loginResultLiveData; + } + + public void login(String host, String username, String password) { + TinyRSSApi tinyRSSApi = new TinyRSSApi(host); + + tinyRSSApi.login(username, password, new CallbackX() { + @Override + public void onSuccess(String auth) { + User user = new User(); + user.setSource(Contract.PROVIDER_TINYRSS); + user.setId(Contract.PROVIDER_TINYRSS + "_" + username); + user.setUserId(username); + user.setUserName(username); + user.setUserPassword(password); + user.setAuth(auth); + user.setExpiresTimestamp(0); + user.setHost(host); + tinyRSSApi.setAuthorization(auth); + App.i().getKeyValue().putString(Contract.UID, user.getId()); + KLog.i("登录成功:" + user.getId()); + User userTmp = userDao.getById(user.getId()); + if (userTmp != null) { + CoreDB.i().userDao().update(user); + }else { + CoreDB.i().userDao().insert(user); + } + + LoginResult loginResult = new LoginResult().setSuccess(true).setData(auth); + loginResultLiveData.postValue(loginResult); + } + + @Override + public void onFailure(String error) { + LoginResult loginResult = new LoginResult().setSuccess(false).setData(App.i().getString(R.string.login_failed_reason, error)); + loginResultLiveData.postValue(loginResult); + } + }); + } + + public void loginDataChanged(String host, String username, String password) { + LoginFormState loginFormState = new LoginFormState(); + + if (!isHostValid(host)) { + loginFormState.setHostHint(R.string.invalid_host); + } else if (!isUserNameValid(username)) { + loginFormState.setUsernameHint(R.string.invalid_username); + } else if (!isPasswordValid(password)) { + loginFormState.setPasswordHint(R.string.invalid_password); + } else { + loginFormState.setDataValid(true); + } + + loginFormLiveData.setValue(loginFormState); + } + + private boolean isHostValid(String host) { + return !TextUtils.isEmpty(host) && Patterns.WEB_URL.matcher(host).matches(); + } + + // A placeholder username validation check + private boolean isUserNameValid(String username) { + return !TextUtils.isEmpty(username) && !username.trim().isEmpty(); + } + + // A placeholder password validation check + private boolean isPasswordValid(String password) { + return !TextUtils.isEmpty(password) && password.trim().length() > 5; + } +} diff --git a/app/src/main/res/drawable-v23/logo_feedly_icon.xml b/app/src/main/res/drawable-v23/logo_feedly_icon.xml new file mode 100644 index 0000000..8f3b33c --- /dev/null +++ b/app/src/main/res/drawable-v23/logo_feedly_icon.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/drawable-v23/logo_inoreader_icon.xml b/app/src/main/res/drawable-v23/logo_inoreader_icon.xml new file mode 100644 index 0000000..486a0ee --- /dev/null +++ b/app/src/main/res/drawable-v23/logo_inoreader_icon.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/drawable-v23/logo_ttrss_icon.xml b/app/src/main/res/drawable-v23/logo_ttrss_icon.xml new file mode 100644 index 0000000..2a52c5a --- /dev/null +++ b/app/src/main/res/drawable-v23/logo_ttrss_icon.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/drawable/corners_bg_checked.xml b/app/src/main/res/drawable/corners_bg_checked.xml new file mode 100644 index 0000000..abf5644 --- /dev/null +++ b/app/src/main/res/drawable/corners_bg_checked.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/corners_bg_uncheck.xml b/app/src/main/res/drawable/corners_bg_uncheck.xml new file mode 100644 index 0000000..ecbc998 --- /dev/null +++ b/app/src/main/res/drawable/corners_bg_uncheck.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/custom_progress_bar_thumb.xml b/app/src/main/res/drawable/custom_progress_bar_thumb.xml new file mode 100644 index 0000000..c210255 --- /dev/null +++ b/app/src/main/res/drawable/custom_progress_bar_thumb.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/custom_thumb_src.xml b/app/src/main/res/drawable/custom_thumb_src.xml new file mode 100644 index 0000000..2af6679 --- /dev/null +++ b/app/src/main/res/drawable/custom_thumb_src.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dialog_ic_copy_link.xml b/app/src/main/res/drawable/dialog_ic_copy_link.xml deleted file mode 100644 index 2caf197..0000000 --- a/app/src/main/res/drawable/dialog_ic_copy_link.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/dialog_ic_open_in_new_window.xml b/app/src/main/res/drawable/dialog_ic_open_in_new_window.xml deleted file mode 100644 index 6e1198f..0000000 --- a/app/src/main/res/drawable/dialog_ic_open_in_new_window.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/dialog_ic_redownload.xml b/app/src/main/res/drawable/dialog_ic_redownload.xml deleted file mode 100644 index e73d9a5..0000000 --- a/app/src/main/res/drawable/dialog_ic_redownload.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_arrow_auto_mark_readed.xml b/app/src/main/res/drawable/ic_arrow_auto_mark_readed.xml new file mode 100644 index 0000000..3b96adf --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_auto_mark_readed.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml index c63eeb5..8f8f1d0 100644 --- a/app/src/main/res/drawable/ic_close.xml +++ b/app/src/main/res/drawable/ic_close.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_eye.xml b/app/src/main/res/drawable/ic_eye.xml new file mode 100644 index 0000000..d82fc46 --- /dev/null +++ b/app/src/main/res/drawable/ic_eye.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_favor.xml b/app/src/main/res/drawable/ic_favor.xml new file mode 100644 index 0000000..38f785a --- /dev/null +++ b/app/src/main/res/drawable/ic_favor.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_favor_fill.xml b/app/src/main/res/drawable/ic_favor_fill.xml new file mode 100644 index 0000000..7f246eb --- /dev/null +++ b/app/src/main/res/drawable/ic_favor_fill.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/dialog_ic_mark_down.xml b/app/src/main/res/drawable/ic_mark_down.xml similarity index 100% rename from app/src/main/res/drawable/dialog_ic_mark_down.xml rename to app/src/main/res/drawable/ic_mark_down.xml diff --git a/app/src/main/res/drawable/dialog_ic_mark_unread.xml b/app/src/main/res/drawable/ic_mark_unread.xml similarity index 100% rename from app/src/main/res/drawable/dialog_ic_mark_unread.xml rename to app/src/main/res/drawable/ic_mark_unread.xml diff --git a/app/src/main/res/drawable/dialog_ic_mark_up.xml b/app/src/main/res/drawable/ic_mark_up.xml similarity index 100% rename from app/src/main/res/drawable/dialog_ic_mark_up.xml rename to app/src/main/res/drawable/ic_mark_up.xml diff --git a/app/src/main/res/drawable/ic_music.xml b/app/src/main/res/drawable/ic_music.xml new file mode 100644 index 0000000..2d03c87 --- /dev/null +++ b/app/src/main/res/drawable/ic_music.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_navigate_before.xml b/app/src/main/res/drawable/ic_navigate_before.xml deleted file mode 100644 index 92af7ed..0000000 --- a/app/src/main/res/drawable/ic_navigate_before.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_panorama.xml b/app/src/main/res/drawable/ic_panorama.xml new file mode 100644 index 0000000..0b9c0b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_panorama.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_read.xml b/app/src/main/res/drawable/ic_read.xml new file mode 100644 index 0000000..411714f --- /dev/null +++ b/app/src/main/res/drawable/ic_read.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/dialog_ic_rename.xml b/app/src/main/res/drawable/ic_rename.xml similarity index 100% rename from app/src/main/res/drawable/dialog_ic_rename.xml rename to app/src/main/res/drawable/ic_rename.xml diff --git a/app/src/main/res/drawable/state_all.xml b/app/src/main/res/drawable/ic_state_all.xml similarity index 100% rename from app/src/main/res/drawable/state_all.xml rename to app/src/main/res/drawable/ic_state_all.xml diff --git a/app/src/main/res/drawable/state_star.xml b/app/src/main/res/drawable/ic_state_star.xml similarity index 100% rename from app/src/main/res/drawable/state_star.xml rename to app/src/main/res/drawable/ic_state_star.xml diff --git a/app/src/main/res/drawable/state_unread.xml b/app/src/main/res/drawable/ic_state_unread.xml similarity index 100% rename from app/src/main/res/drawable/state_unread.xml rename to app/src/main/res/drawable/ic_state_unread.xml diff --git a/app/src/main/res/drawable/ic_state_unstar.xml b/app/src/main/res/drawable/ic_state_unstar.xml new file mode 100644 index 0000000..45fbba9 --- /dev/null +++ b/app/src/main/res/drawable/ic_state_unstar.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_stop_loading.xml b/app/src/main/res/drawable/ic_stop_loading.xml new file mode 100644 index 0000000..e5e8b5d --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_loading.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/dialog_ic_unsubscribe.xml b/app/src/main/res/drawable/ic_unsubscribe.xml similarity index 100% rename from app/src/main/res/drawable/dialog_ic_unsubscribe.xml rename to app/src/main/res/drawable/ic_unsubscribe.xml diff --git a/app/src/main/res/drawable/ic_volume.xml b/app/src/main/res/drawable/ic_volume.xml new file mode 100644 index 0000000..edeca1f --- /dev/null +++ b/app/src/main/res/drawable/ic_volume.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/logo_feedly.xml b/app/src/main/res/drawable/logo_feedly.xml new file mode 100644 index 0000000..cd5d1e8 --- /dev/null +++ b/app/src/main/res/drawable/logo_feedly.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/logo_inoreader.xml b/app/src/main/res/drawable/logo_inoreader.xml new file mode 100644 index 0000000..9f3f046 --- /dev/null +++ b/app/src/main/res/drawable/logo_inoreader.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/drawable/logo_tinytinyrss.png b/app/src/main/res/drawable/logo_tinytinyrss.png new file mode 100644 index 0000000..421e109 Binary files /dev/null and b/app/src/main/res/drawable/logo_tinytinyrss.png differ diff --git a/app/src/main/res/drawable/md_btn_selected_custom.xml b/app/src/main/res/drawable/md_btn_selected_custom.xml deleted file mode 100644 index f40917f..0000000 --- a/app/src/main/res/drawable/md_btn_selected_custom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/md_btn_selector_custom.xml b/app/src/main/res/drawable/md_btn_selector_custom.xml deleted file mode 100644 index 6a56229..0000000 --- a/app/src/main/res/drawable/md_btn_selector_custom.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/md_btn_unselected_custom.xml b/app/src/main/res/drawable/md_btn_unselected_custom.xml deleted file mode 100644 index 4b7c833..0000000 --- a/app/src/main/res/drawable/md_btn_unselected_custom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_bg.xml b/app/src/main/res/drawable/progress_bg.xml new file mode 100644 index 0000000..ad7fbf1 --- /dev/null +++ b/app/src/main/res/drawable/progress_bg.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/scrollbar_dark.xml b/app/src/main/res/drawable/scrollbar_dark.xml deleted file mode 100644 index f3438a7..0000000 --- a/app/src/main/res/drawable/scrollbar_dark.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/scrollbar_light.xml b/app/src/main/res/drawable/scrollbar_light.xml deleted file mode 100644 index d42103c..0000000 --- a/app/src/main/res/drawable/scrollbar_light.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/seekbar_audio.xml b/app/src/main/res/drawable/seekbar_audio.xml new file mode 100644 index 0000000..33dde48 --- /dev/null +++ b/app/src/main/res/drawable/seekbar_audio.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_corners_bg.xml b/app/src/main/res/drawable/selector_corners_bg.xml new file mode 100644 index 0000000..251114a --- /dev/null +++ b/app/src/main/res/drawable/selector_corners_bg.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_star.xml b/app/src/main/res/drawable/selector_star.xml new file mode 100644 index 0000000..d8ac6af --- /dev/null +++ b/app/src/main/res/drawable/selector_star.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/slv_menu_ic_adjust.xml b/app/src/main/res/drawable/slv_menu_ic_adjust.xml deleted file mode 100644 index 6c345a1..0000000 --- a/app/src/main/res/drawable/slv_menu_ic_adjust.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/splash_layers.xml b/app/src/main/res/drawable/splash_layers.xml index 4111148..6a4a719 100644 --- a/app/src/main/res/drawable/splash_layers.xml +++ b/app/src/main/res/drawable/splash_layers.xml @@ -1,6 +1,6 @@ - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/textview_border_night.xml b/app/src/main/res/drawable/textview_border_night.xml new file mode 100644 index 0000000..ed1ca0a --- /dev/null +++ b/app/src/main/res/drawable/textview_border_night.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_article.xml b/app/src/main/res/layout/activity_article.xml index 571232e..6d1d631 100644 --- a/app/src/main/res/layout/activity_article.xml +++ b/app/src/main/res/layout/activity_article.xml @@ -1,56 +1,63 @@ - - - - - - - - - - - + app:popupTheme="@style/OverflowMenuStyle" + tools:navigationIcon="@drawable/ic_close" + tools:title="源标题"> - + android:layout_gravity="end|center" + android:padding="5dp" + android:textSize="14sp" + android:layout_marginEnd="10dp" + android:textColor="?attr/topbar_fg" + android:visibility="gone" /> - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + android:layout_height="match_parent" + android:background="?attr/root_view_bg"> + + - - - - - - - - + android:text="@string/font_chrome" + android:onClick="clickOpenOriginalArticle" /> + + android:onClick="onClickStarIcon" /> - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/activity_article_old.xml b/app/src/main/res/layout/activity_article_old.xml deleted file mode 100644 index 9a18273..0000000 --- a/app/src/main/res/layout/activity_article_old.xml +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_feed.xml b/app/src/main/res/layout/activity_feed.xml new file mode 100644 index 0000000..6f0e07b --- /dev/null +++ b/app/src/main/res/layout/activity_feed.xml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml deleted file mode 100644 index a894e51..0000000 --- a/app/src/main/res/layout/activity_image.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/activity_lab.xml b/app/src/main/res/layout/activity_lab.xml new file mode 100644 index 0000000..7a3a2f4 --- /dev/null +++ b/app/src/main/res/layout/activity_lab.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml deleted file mode 100644 index 87b965e..0000000 --- a/app/src/main/res/layout/activity_login.xml +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -