diff --git a/app/build.gradle b/app/build.gradle index 6d576b873..f19373953 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -152,7 +152,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" //room - def room_version = '2.2.6' + def room_version = '2.3.0' implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" testImplementation "androidx.room:room-testing:$room_version" @@ -178,10 +178,7 @@ dependencies { implementation 'com.github.gedoor:rhino-android:1.5' //网络 - //noinspection GradleDependency implementation 'com.squareup.okhttp3:okhttp:4.9.1' - implementation 'com.ljx.rxhttp:rxhttp:2.5.7' - kapt 'com.ljx.rxhttp:rxhttp-compiler:2.5.7' //Glide implementation 'com.github.bumptech.glide:glide:4.11.0' @@ -191,7 +188,7 @@ dependencies { implementation 'org.nanohttpd:nanohttpd-websocket:2.3.1' //二维码 - implementation 'com.king.zxing:zxing-lite:2.0.2' + implementation 'com.king.zxing:zxing-lite:2.0.3' //颜色选择 implementation 'com.jaredrummler:colorpicker:1.1.0' diff --git a/app/src/main/assets/defaultData/httpTTS.json b/app/src/main/assets/defaultData/httpTTS.json index de70e5196..08d3386cd 100644 --- a/app/src/main/assets/defaultData/httpTTS.json +++ b/app/src/main/assets/defaultData/httpTTS.json @@ -1,147 +1,237 @@ [ + { + "id": -100, + "name": "0", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4127&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=11&vol=5&aue=6&pit=3&_res_tag_=audio\"\n}" + }, + { + "id": -99, + "name": "zaixianai.cn", + "url": "\nlet url='https://www.zaixianai.cn/voiceCompose';\n\nlet ua=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36\";\n\nlet doc=java.get(url,{\"User-Agent\":ua});\nlet cookie=String(doc.header(\"set-cookie\")).match(/laravel_session=[^\\n]+/)[0];\nlet token=String(doc.body()).match(/token=\"([^\"]+)/)[1];\n\nurl='https://www.zaixianai.cn/Api_getVoice,'+JSON.stringify({\n\"method\": \"POST\",\n\"body\": \"content=\" + java.encodeURI(speakText) + \"&volume=50&speech_rate=0&voice=Aixia&_token=\"+token,\n\"headers\": {\n\"User-Agent\": ua,\n\"cookie\": cookie\n}\n});\n\nlet res=java.ajax(url);\n\n'https://www.zaixianai.cn/voice/'+JSON.parse(res).data.file_name+','+JSON.stringify({\n\"headers\": {\n\"User-Agent\": ua,\n\"accept\": \"*/*\",\n\"referer\": \"https://www.zaixianai.cn/voiceCompose\",\n\"cookie\": cookie,\n\"accept-encoding\": \"identity;q=1, *;q=0\"\n}\n})\n" + }, + { + "id": -98, + "name": "台湾女声", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4007&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, { "id": -1, "name": "度丫丫", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -2, "name": "度博文①", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -3, "name": "度博文②", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -4, "name": "度博文③", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -97, + "name": "度小乔", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=1117&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=3&_res_tag_=audio\"\n}" }, { "id": -5, "name": "度小娇", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -6, "name": "度小宇", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=1&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=2&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -7, "name": "度小童", "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=110&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" }, + { + "id": -96, + "name": "度小童", + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=110&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -95, + "name": "度小粤", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=0&cuid=baidu_speech_demo&idx=1&cod=2&lan=cte&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, { "id": -8, "name": "度小美", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=0&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=0&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -94, + "name": "度小芳", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4125&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -9, "name": "度小萌", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=111&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=111&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -93, + "name": "度小贤", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4115&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"}" + }, + { + "id": -92, + "name": "度小雯", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5100&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -10, "name": "度小鹿①", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4118&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4118&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { - "id": -11, + "id": -12, "name": "度小鹿②", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5118&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4119&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { - "id": -12, + "id": -11, "name": "度小鹿③", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4119&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5118&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -91, + "name": "度灵儿", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5105&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -13, "name": "度米朵①", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=103&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=103&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -14, "name": "度米朵②", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4103&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4103&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -15, "name": "度逍遥-基础", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=3&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=3&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -16, "name": "度逍遥-精品①", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4003&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4003&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -17, "name": "度逍遥-精品②", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5003&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5003&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -18, "name": "情感女声", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4105&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4105&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -19, "name": "情感男声", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4115&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4115&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -20, "name": "标准女声", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4100&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4100&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -90, + "name": "标准女声-基础", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=100&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -21, "name": "标准男声", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4121&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4121&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160=&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { - "id": -22, - "name": "治愈女声", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5120&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "id": -89, + "name": "温柔女声", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4126&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { - "id": -23, - "name": "治愈男声", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5121&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "id": -88, + "name": "甜美女声①", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=1200&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=&rate=32=5&pit=5&_res_tag_=audio\"\n}" }, { "id": -24, - "name": "甜美女声①", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4117&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "name": "甜美女声②", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4117&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -25, - "name": "甜美女声②", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5117&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "name": "甜美女声③", + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5117&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -22, + "name": "电台女声", + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5120&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=&rate=32=5&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -23, + "name": "电台男声", + "url": "http://tsn.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5121&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -26, "name": "百度主持", "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=9&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" }, + { + "id": -87, + "name": "百度主持", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4127&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=11&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -86, + "name": "百度解说①", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4123&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=12&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -85, + "name": "百度解说②", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4128&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=12&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -84, + "name": "百度解说③", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4129&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=12&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, { "id": -27, "name": "百度评书①", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=6&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=6&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -28, "name": "百度评书②", - "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4114&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&pit=5&_res_tag_=audio\"\n}" + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4114&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" + }, + { + "id": -83, + "name": "萝莉少女音", + "url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=5201&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\"\n}" }, { "id": -29, "name": "阿里云语音", "url": "/*播音人Aiting可改其他https://cdn.jsdelivr.net/gh/Celeter/build/.github/scripts/speaker.json,详见https://ai.aliyun.com/nls/tts*/;eval(''+java.ajax('https://cdn.jsdelivr.net/gh/Celeter/build/.github/scripts/ttsDemo.js'));ttsDemo(speakText,speakSpeed,'Aiting')" } -] +] \ No newline at end of file diff --git a/app/src/main/assets/defaultData/readConfig.json b/app/src/main/assets/defaultData/readConfig.json index 968141a0c..cd62a05be 100644 --- a/app/src/main/assets/defaultData/readConfig.json +++ b/app/src/main/assets/defaultData/readConfig.json @@ -1,5 +1,56 @@ [ { + "bgStr": "#ffc0edc6", + "bgStrEInk": "#FFFFFF", + "bgStrNight": "#000000", + "bgType": 0, + "bgTypeEInk": 0, + "bgTypeNight": 0, + "darkStatusIcon": true, + "darkStatusIconEInk": true, + "darkStatusIconNight": false, + "footerMode": 0, + "footerPaddingBottom": 10, + "footerPaddingLeft": 13, + "footerPaddingRight": 17, + "footerPaddingTop": 0, + "headerMode": 0, + "headerPaddingBottom": 0, + "headerPaddingLeft": 19, + "headerPaddingRight": 16, + "headerPaddingTop": 10, + "letterSpacing": 0, + "lineSpacingExtra": 10, + "name": "微信读书", + "paddingBottom": 4, + "paddingLeft": 22, + "paddingRight": 22, + "paddingTop": 5, + "pageAnim": 3, + "pageAnimEInk": 3, + "paragraphIndent": "  ", + "paragraphSpacing": 6, + "showFooterLine": true, + "showHeaderLine": true, + "textBold": 0, + "textColor": "#ff0b0b0b", + "textColorEInk": "#000000", + "textColorNight": "#ADADAD", + "textSize": 24, + "tipColor": -10461088, + "tipFooterLeft": 7, + "tipFooterMiddle": 0, + "tipFooterRight": 6, + "tipHeaderLeft": 1, + "tipHeaderMiddle": 0, + "tipHeaderRight": 2, + "titleBottomSpacing": 0, + "titleMode": 0, + "titleSize": 4, + "titleTopSpacing": 0 + }, + { + "name": "预设1", "bgStr": "#FFFFFF", "bgStrNight": "#000000", "textColor": "#000000", @@ -10,6 +61,7 @@ "darkStatusIconNight": false }, { + "name": "预设2", "bgStr": "#DDC090", "bgStrNight": "#3C3F43", "textColor": "#3E3422", @@ -20,6 +72,7 @@ "darkStatusIconNight": false }, { + "name": "预设3", "bgStr": "#C2D8AA", "bgStrNight": "#3C3F43", "textColor": "#596C44", @@ -30,6 +83,7 @@ "darkStatusIconNight": false }, { + "name": "预设4", "bgStr": "#DBB8E2", "bgStrNight": "#3C3F43", "textColor": "#68516C", @@ -40,6 +94,7 @@ "darkStatusIconNight": false }, { + "name": "预设5", "bgStr": "#ABCEE0", "bgStrNight": "#3C3F43", "textColor": "#3D4C54", diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 8b4c3a008..11a6dcfcd 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -3,12 +3,22 @@ * 关注合作公众号 **[小说拾遗]** 获取好看的小说。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 -**2021/04/18** -* epub插图,epublib优化 by ag2s20150909 +**2021/05/04** +* 修复bug +* url参数添加重置次数,retry +* 修改默认tts, 手动导入 +* 升级android studio + +**2021/04/30** +* epub插图,epublib优化,图片解码优化,epub读取导出优化。by ag2s20150909 +* 添加高刷设置 +* 其它一些优化 +* pro版本被play商店下架了,先把pro设置图片背景的功能开放到所有版本,使用pro版本的可以使用备份恢复功能切换最新版本 **2021/04/16** * 去掉google统计,解决华为手机使用崩溃的bug * 添加规则订阅时判断重复提醒 +* 添加恢复预设布局的功能, 添加一个微信读书布局作为预设布局 **2021/04/13** * 修复导入布局bug diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 6345cf1b1..5791d7007 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -7,7 +7,6 @@ import android.content.res.Configuration import android.os.Build import androidx.multidex.MultiDexApplication import com.jeremyliao.liveeventbus.LiveEventBus -import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst.channelIdDownload import io.legado.app.constant.AppConst.channelIdReadAloud import io.legado.app.constant.AppConst.channelIdWeb @@ -15,10 +14,8 @@ import io.legado.app.help.ActivityHelp import io.legado.app.help.AppConfig import io.legado.app.help.CrashHandler import io.legado.app.help.ThemeConfig.applyDayNight -import io.legado.app.help.http.HttpHelper import io.legado.app.utils.LanguageUtils import io.legado.app.utils.defaultSharedPreferences -import rxhttp.wrapper.param.RxHttp class App : MultiDexApplication() { @@ -26,10 +23,6 @@ class App : MultiDexApplication() { super.onCreate() CrashHandler(this) LanguageUtils.setConfiguration(this) - RxHttp.init(HttpHelper.client, BuildConfig.DEBUG) - RxHttp.setOnParamAssembly { - it.addHeader(AppConst.UA_NAME, AppConfig.userAgent) - } createNotificationChannels() applyDayNight(this) LiveEventBus.config() diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index ef9c23953..4d2707444 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -14,6 +14,7 @@ import androidx.viewbinding.ViewBinding import io.legado.app.App import io.legado.app.R import io.legado.app.constant.AppConst +import io.legado.app.constant.PreferKey import io.legado.app.constant.Theme import io.legado.app.help.AppConfig import io.legado.app.help.ThemeConfig @@ -66,6 +67,26 @@ abstract class BaseActivity( } override fun onCreate(savedInstanceState: Bundle?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + getPrefBoolean(PreferKey.highBrush) + ) { + /** + * 添加高刷新率支持 + */ + // 获取系统window支持的模式 + @Suppress("DEPRECATION") + val modes = window.windowManager.defaultDisplay.supportedModes + // 对获取的模式,基于刷新率的大小进行排序,从小到大排序 + modes.sortBy { + it.refreshRate + } + window.let { + val lp = it.attributes + // 取出最大的那一个刷新率,直接设置给window + lp.preferredDisplayModeId = modes.last().modeId + it.attributes = lp + } + } window.decorView.disableAutoFill() initTheme() super.onCreate(savedInstanceState) @@ -152,11 +173,9 @@ abstract class BaseActivity( ATH.applyBackgroundTint(window.decorView) } } - if (AppConfig.isGooglePlay) { - ThemeConfig.getBgImage(this)?.let { - kotlin.runCatching { - window.decorView.background = it - } + ThemeConfig.getBgImage(this)?.let { + kotlin.runCatching { + window.decorView.background = it } } } diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 0d1e5ef7e..651830256 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -73,6 +73,7 @@ object PreferKey { const val exportUseReplace = "exportUseReplace" const val useZhLayout = "useZhLayout" const val fullScreenGesturesSupport = "fullScreenGesturesSupport" + const val highBrush = "highBrush" const val cPrimary = "colorPrimary" const val cAccent = "colorAccent" diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 70c05de42..8add07077 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -10,6 +10,7 @@ import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst.androidId import io.legado.app.data.dao.* import io.legado.app.data.entities.* +import io.legado.app.help.AppConfig import splitties.init.appCtx import java.util.* @@ -88,6 +89,13 @@ abstract class AppDatabase : RoomDatabase() { """insert into book_groups(groupId, groupName, 'order', show) select ${AppConst.bookGroupNoneId}, '未分组', -7, 1 where not exists (select * from book_groups where groupId = ${AppConst.bookGroupNoneId})""" ) + if (AppConfig.isGooglePlay) { + db.execSQL( + """ + delete from rssSources where sourceUrl = 'https://github.com/gedoor/legado/releases' + """ + ) + } } } diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index 5093c05c1..2ee782122 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -107,6 +107,8 @@ data class Book( fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) + fun getDisplayTag() = if (customTag.isNullOrBlank()) kind else customTag + fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl fun getDisplayIntro() = if (customIntro.isNullOrEmpty()) intro else customIntro diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 64d8d0893..851660fdf 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -4,9 +4,7 @@ import android.net.Uri import android.util.Base64 import androidx.annotation.Keep import io.legado.app.constant.AppConst.dateFormat -import io.legado.app.help.http.CookieStore -import io.legado.app.help.http.SSLHelper -import io.legado.app.help.http.StrResponse +import io.legado.app.help.http.* import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.QueryTTF @@ -16,8 +14,6 @@ import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.jsoup.Connection import org.jsoup.Jsoup -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toByteArray import splitties.init.appCtx import java.io.File import java.net.URLEncoder @@ -292,7 +288,7 @@ interface JsExtensions { str.isAbsUrl() -> runBlocking { var x = CacheManager.getByteArray(key) if (x == null) { - x = RxHttp.get(str).toByteArray().await() + x = okHttpClient.newCall { url(str) }.bytes() x.let { CacheManager.put(key, it) } diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index 6a9f7f8b9..cdf6a03bc 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -11,82 +11,28 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import kotlin.coroutines.resume -@Suppress("unused") -object HttpHelper { - - private val proxyClientCache: ConcurrentHashMap by lazy { - ConcurrentHashMap() - } - - val client: OkHttpClient by lazy { - - val specs = arrayListOf( - ConnectionSpec.MODERN_TLS, - ConnectionSpec.COMPATIBLE_TLS, - ConnectionSpec.CLEARTEXT - ) - - val builder = OkHttpClient.Builder() - .connectTimeout(15, TimeUnit.SECONDS) - .writeTimeout(15, TimeUnit.SECONDS) - .readTimeout(15, TimeUnit.SECONDS) - .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager) - .retryOnConnectionFailure(true) - .hostnameVerifier(SSLHelper.unsafeHostnameVerifier) - .connectionSpecs(specs) - .followRedirects(true) - .followSslRedirects(true) - .addInterceptor(getHeaderInterceptor()) - - builder.build() - } +private val proxyClientCache: ConcurrentHashMap by lazy { + ConcurrentHashMap() +} - /** - * 缓存代理okHttp - */ - fun getProxyClient(proxy: String? = null): OkHttpClient { - if (proxy.isNullOrBlank()) { - return client - } - proxyClientCache[proxy]?.let { - return it - } - val r = Regex("(http|socks4|socks5)://(.*):(\\d{2,5})(@.*@.*)?") - val ms = r.findAll(proxy) - val group = ms.first() - var username = "" //代理服务器验证用户名 - var password = "" //代理服务器验证密码 - val type = if (group.groupValues[1] == "http") "http" else "socks" - val host = group.groupValues[2] - val port = group.groupValues[3].toInt() - if (group.groupValues[4] != "") { - username = group.groupValues[4].split("@")[1] - password = group.groupValues[4].split("@")[2] - } - if (type != "direct" && host != "") { - val builder = client.newBuilder() - if (type == "http") { - builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port))) - } else { - builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port))) - } - if (username != "" && password != "") { - builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码 - val credential: String = Credentials.basic(username, password) - response.request.newBuilder() - .header("Proxy-Authorization", credential) - .build() - } - } - val proxyClient = builder.build() - proxyClientCache[proxy] = proxyClient - return proxyClient - } - return client - } +val okHttpClient: OkHttpClient by lazy { + val specs = arrayListOf( + ConnectionSpec.MODERN_TLS, + ConnectionSpec.COMPATIBLE_TLS, + ConnectionSpec.CLEARTEXT + ) - private fun getHeaderInterceptor(): Interceptor { - return Interceptor { chain -> + val builder = OkHttpClient.Builder() + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager) + .retryOnConnectionFailure(true) + .hostnameVerifier(SSLHelper.unsafeHostnameVerifier) + .connectionSpecs(specs) + .followRedirects(true) + .followSslRedirects(true) + .addInterceptor(Interceptor { chain -> val request = chain.request() .newBuilder() .addHeader("Keep-Alive", "300") @@ -94,28 +40,72 @@ object HttpHelper { .addHeader("Cache-Control", "no-cache") .build() chain.proceed(request) + }) + + builder.build() +} + +/** + * 缓存代理okHttp + */ +fun getProxyClient(proxy: String? = null): OkHttpClient { + if (proxy.isNullOrBlank()) { + return okHttpClient + } + proxyClientCache[proxy]?.let { + return it + } + val r = Regex("(http|socks4|socks5)://(.*):(\\d{2,5})(@.*@.*)?") + val ms = r.findAll(proxy) + val group = ms.first() + var username = "" //代理服务器验证用户名 + var password = "" //代理服务器验证密码 + val type = if (group.groupValues[1] == "http") "http" else "socks" + val host = group.groupValues[2] + val port = group.groupValues[3].toInt() + if (group.groupValues[4] != "") { + username = group.groupValues[4].split("@")[1] + password = group.groupValues[4].split("@")[2] + } + if (type != "direct" && host != "") { + val builder = okHttpClient.newBuilder() + if (type == "http") { + builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port))) + } else { + builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port))) + } + if (username != "" && password != "") { + builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码 + val credential: String = Credentials.basic(username, password) + response.request.newBuilder() + .header("Proxy-Authorization", credential) + .build() + } } + val proxyClient = builder.build() + proxyClientCache[proxy] = proxyClient + return proxyClient } + return okHttpClient +} - suspend fun ajax(params: AjaxWebView.AjaxParams): StrResponse = - suspendCancellableCoroutine { block -> - val webView = AjaxWebView() - block.invokeOnCancellation { - webView.destroyWebView() - } - webView.callback = object : AjaxWebView.Callback() { - override fun onResult(response: StrResponse) { +suspend fun getWebViewSrc(params: AjaxWebView.AjaxParams): StrResponse = + suspendCancellableCoroutine { block -> + val webView = AjaxWebView() + block.invokeOnCancellation { + webView.destroyWebView() + } + webView.callback = object : AjaxWebView.Callback() { + override fun onResult(response: StrResponse) { - if (!block.isCompleted) - block.resume(response) - } + if (!block.isCompleted) + block.resume(response) + } - override fun onError(error: Throwable) { - if (!block.isCompleted) - block.cancel(error) - } + override fun onError(error: Throwable) { + if (!block.isCompleted) + block.cancel(error) } - webView.load(params) } - -} + webView.load(params) + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/OkHttpUtils.kt b/app/src/main/java/io/legado/app/help/http/OkHttpUtils.kt new file mode 100644 index 000000000..4dec4bf3e --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/OkHttpUtils.kt @@ -0,0 +1,122 @@ +package io.legado.app.help.http + +import io.legado.app.constant.AppConst +import io.legado.app.help.AppConfig +import io.legado.app.utils.EncodingDetect +import io.legado.app.utils.UTF8BOMFighter +import kotlinx.coroutines.suspendCancellableCoroutine +import okhttp3.* +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import java.nio.charset.Charset +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +suspend fun OkHttpClient.newCall( + retry: Int = 0, + builder: Request.Builder.() -> Unit +): ResponseBody { + val requestBuilder = Request.Builder() + requestBuilder.header(AppConst.UA_NAME, AppConfig.userAgent) + requestBuilder.apply(builder) + var response: Response? = null + for (i in 0..retry) { + response = this.newCall(requestBuilder.build()).await() + if (response.isSuccessful) { + return response.body!! + } + } + throw IOException(response!!.message) +} + +suspend fun OkHttpClient.newCallStrResponse( + retry: Int = 0, + builder: Request.Builder.() -> Unit +): StrResponse { + val requestBuilder = Request.Builder() + requestBuilder.header(AppConst.UA_NAME, AppConfig.userAgent) + requestBuilder.apply(builder) + var response: Response? = null + for (i in 0..retry) { + response = this.newCall(requestBuilder.build()).await() + if (response.isSuccessful) { + return StrResponse(response, response.body!!.text()) + } + } + throw IOException(response!!.message) +} + +suspend fun Call.await(): Response = suspendCancellableCoroutine { block -> + + block.invokeOnCancellation { + cancel() + } + + enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + block.resumeWithException(e) + } + + override fun onResponse(call: Call, response: Response) { + block.resume(response) + } + }) + +} + +fun ResponseBody.text(encode: String? = null): String { + val responseBytes = UTF8BOMFighter.removeUTF8BOM(bytes()) + var charsetName: String? = encode + + charsetName?.let { + return String(responseBytes, Charset.forName(charsetName)) + } + + //根据http头判断 + contentType()?.charset()?.let { + return String(responseBytes, it) + } + + //根据内容判断 + charsetName = EncodingDetect.getHtmlEncode(responseBytes) + return String(responseBytes, Charset.forName(charsetName)) +} + +fun Request.Builder.addHeaders(headers: Map) { + headers.forEach { + addHeader(it.key, it.value) + } +} + +fun Request.Builder.get(url: String, queryMap: Map, encoded: Boolean = false) { + val httpBuilder = url.toHttpUrl().newBuilder() + queryMap.forEach { + if (encoded) { + httpBuilder.addEncodedQueryParameter(it.key, it.value) + } else { + httpBuilder.addQueryParameter(it.key, it.value) + } + } + url(httpBuilder.build()) +} + +fun Request.Builder.postForm(form: Map, encoded: Boolean = false) { + val formBody = FormBody.Builder() + form.forEach { + if (encoded) { + formBody.addEncoded(it.key, it.value) + } else { + formBody.add(it.key, it.value) + } + } + post(formBody.build()) +} + +fun Request.Builder.postJson(json: String?) { + json?.let { + val requestBody = json.toRequestBody("application/json; charset=UTF-8".toMediaType()) + post(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/RetryInterceptor.kt b/app/src/main/java/io/legado/app/help/http/RetryInterceptor.kt new file mode 100644 index 000000000..00c4a0881 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/RetryInterceptor.kt @@ -0,0 +1,12 @@ +package io.legado.app.help.http + +import okhttp3.Interceptor +import okhttp3.Response + +class RetryInterceptor : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/parser/ByteParser.kt b/app/src/main/java/io/legado/app/help/http/parser/ByteParser.kt deleted file mode 100644 index b2812d41e..000000000 --- a/app/src/main/java/io/legado/app/help/http/parser/ByteParser.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.legado.app.help.http.parser - -import okhttp3.Response -import rxhttp.wrapper.annotation.Parser - -@Parser(name = "ByteArray") -class ByteParser : rxhttp.wrapper.parse.Parser { - - override fun onParse(response: Response): ByteArray { - return response.body!!.bytes() - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/parser/InputStreamParser.kt b/app/src/main/java/io/legado/app/help/http/parser/InputStreamParser.kt deleted file mode 100644 index 9ca0ffe4d..000000000 --- a/app/src/main/java/io/legado/app/help/http/parser/InputStreamParser.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.legado.app.help.http.parser - -import okhttp3.Response -import rxhttp.wrapper.annotation.Parser -import java.io.InputStream - -@Parser(name = "InputStream") -class InputStreamParser : rxhttp.wrapper.parse.Parser { - - override fun onParse(response: Response): InputStream { - return response.body!!.byteStream() - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/parser/StrResponseParser.kt b/app/src/main/java/io/legado/app/help/http/parser/StrResponseParser.kt deleted file mode 100644 index 9868b9dc8..000000000 --- a/app/src/main/java/io/legado/app/help/http/parser/StrResponseParser.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.legado.app.help.http.parser - -import io.legado.app.help.http.StrResponse -import io.legado.app.utils.EncodingDetect -import io.legado.app.utils.UTF8BOMFighter -import okhttp3.Response -import rxhttp.wrapper.annotation.Parser -import rxhttp.wrapper.exception.HttpStatusCodeException -import java.nio.charset.Charset - -@Parser(name = "StrResponse") -class StrResponseParser(private val encode: String? = null) : - rxhttp.wrapper.parse.Parser { - - override fun onParse(response: Response): StrResponse { - val body = getString(response) - return StrResponse(response, body) - } - - private fun getString(response: Response): String { - - val responseBody = response.body ?: throw HttpStatusCodeException(response, "内容为空") - val responseBytes = UTF8BOMFighter.removeUTF8BOM(responseBody.bytes()) - var charsetName: String? = encode - - charsetName?.let { - return String(responseBytes, Charset.forName(charsetName)) - } - - //根据http头判断 - responseBody.contentType()?.charset()?.let { - return String(responseBytes, it) - } - - //根据内容判断 - charsetName = EncodingDetect.getHtmlEncode(responseBytes) - return String(responseBytes, Charset.forName(charsetName)) - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt b/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt deleted file mode 100644 index 031ad9d33..000000000 --- a/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt +++ /dev/null @@ -1,33 +0,0 @@ -package io.legado.app.help.http.parser - -import io.legado.app.utils.EncodingDetect -import io.legado.app.utils.UTF8BOMFighter -import okhttp3.Response -import rxhttp.wrapper.annotation.Parser -import rxhttp.wrapper.exception.HttpStatusCodeException -import java.nio.charset.Charset - -@Parser(name = "Text") -class TextParser(private val encode: String? = null) : rxhttp.wrapper.parse.Parser { - - override fun onParse(response: Response): String { - - val responseBody = response.body ?: throw HttpStatusCodeException(response, "内容为空") - val responseBytes = UTF8BOMFighter.removeUTF8BOM(responseBody.bytes()) - var charsetName: String? = encode - - charsetName?.let { - return String(responseBytes, Charset.forName(charsetName)) - } - - //根据http头判断 - responseBody.contentType()?.charset()?.let { - return String(responseBytes, it) - } - - //根据内容判断 - charsetName = EncodingDetect.getHtmlEncode(responseBytes) - return String(responseBytes, Charset.forName(charsetName)) - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/permission/PermissionsCompat.kt b/app/src/main/java/io/legado/app/lib/permission/PermissionsCompat.kt index a2d604c99..f127c77d6 100644 --- a/app/src/main/java/io/legado/app/lib/permission/PermissionsCompat.kt +++ b/app/src/main/java/io/legado/app/lib/permission/PermissionsCompat.kt @@ -29,11 +29,6 @@ class PermissionsCompat private constructor() { return this } - fun requestCode(requestCode: Int): Builder { - request.setRequestCode(requestCode) - return this - } - fun onGranted(callback: () -> Unit): Builder { request.setOnGrantedCallback(object : OnPermissionsGrantedCallback { override fun onPermissionsGranted() { diff --git a/app/src/main/java/io/legado/app/lib/permission/Request.kt b/app/src/main/java/io/legado/app/lib/permission/Request.kt index 2de60d059..a5f3acacd 100644 --- a/app/src/main/java/io/legado/app/lib/permission/Request.kt +++ b/app/src/main/java/io/legado/app/lib/permission/Request.kt @@ -46,10 +46,6 @@ internal class Request : OnRequestPermissionsResultCallback { this.permissions?.addAll(listOf(*permissions)) } - fun setRequestCode(requestCode: Int) { - this.requestCode = requestCode - } - fun setOnGrantedCallback(callback: OnPermissionsGrantedCallback) { grantedCallback = callback } @@ -81,13 +77,10 @@ internal class Request : OnRequestPermissionsResultCallback { if (rationaleResId != 0) source?.context?.getText(rationaleResId) else rationale if (rationale != null) { showSettingDialog(rationale) { - onPermissionsDenied( - requestCode, - deniedPermissions - ) + onPermissionsDenied(deniedPermissions) } } else { - onPermissionsDenied(requestCode, deniedPermissions) + onPermissionsDenied(deniedPermissions) } } } else { @@ -160,7 +153,7 @@ internal class Request : OnRequestPermissionsResultCallback { RequestPlugins.sResultCallback?.onPermissionsGranted() } - private fun onPermissionsDenied(requestCode: Int, deniedPermissions: Array) { + private fun onPermissionsDenied(deniedPermissions: Array) { try { deniedCallback?.onPermissionsDenied(deniedPermissions) } catch (ignore: Exception) { @@ -178,9 +171,9 @@ internal class Request : OnRequestPermissionsResultCallback { val rationale = if (rationaleResId != 0) source?.context?.getText(rationaleResId) else rationale if (rationale != null) { - showSettingDialog(rationale) { onPermissionsDenied(requestCode, deniedPermissions) } + showSettingDialog(rationale) { onPermissionsDenied(deniedPermissions) } } else { - onPermissionsDenied(requestCode, deniedPermissions) + onPermissionsDenied(deniedPermissions) } } else { onPermissionsGranted() @@ -192,7 +185,7 @@ internal class Request : OnRequestPermissionsResultCallback { if (deniedPermissions == null) { onPermissionsGranted() } else { - onPermissionsDenied(this.requestCode, deniedPermissions) + onPermissionsDenied(deniedPermissions) } } diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt index 1570bc793..36713320f 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt +++ b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt @@ -1,14 +1,13 @@ package io.legado.app.lib.webdav -import io.legado.app.help.http.HttpHelper -import io.legado.app.utils.await +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import org.jsoup.Jsoup -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toInputStream import java.io.File import java.io.IOException import java.io.InputStream @@ -23,16 +22,16 @@ class WebDav(urlStr: String) { // 指定返回哪些属性 private const val DIR = """ - - - - - - - - %s - - """ + + + + + + + + %s + + """ } private val url: URL = URL(urlStr) @@ -59,16 +58,9 @@ class WebDav(urlStr: String) { * @return 远程文件是否存在 */ suspend fun indexFileInfo(): Boolean { - propFindResponse(ArrayList())?.let { response -> - if (!response.isSuccessful) { - this.exists = false - return false - } - response.body?.let { - @Suppress("BlockingMethodInNonBlockingContext") - if (it.string().isNotEmpty()) { - return true - } + propFindResponse(ArrayList())?.let { + if (it.text().isNotEmpty()) { + return true } } return false @@ -81,19 +73,13 @@ class WebDav(urlStr: String) { * @return 文件列表 */ suspend fun listFiles(propsList: ArrayList = ArrayList()): List { - propFindResponse(propsList)?.let { response -> - if (response.isSuccessful) { - response.body?.let { body -> - @Suppress("BlockingMethodInNonBlockingContext") - return parseDir(body.string()) - } - } + propFindResponse(propsList)?.text()?.let { body -> + return parseDir(body) } return ArrayList() } - @Throws(IOException::class) - private suspend fun propFindResponse(propsList: ArrayList, depth: Int = 1): Response? { + private suspend fun propFindResponse(propsList: ArrayList): ResponseBody? { val requestProps = StringBuilder() for (p in propsList) { requestProps.append("\n") @@ -103,19 +89,21 @@ class WebDav(urlStr: String) { } else { String.format(DIR, requestProps.toString() + "\n") } - httpUrl?.let { url -> - // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性 - // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。 - val requestBody = requestPropsStr.toRequestBody("text/plain".toMediaType()) - val request = Request.Builder() - .url(url) - .method("PROPFIND", requestBody) - - HttpAuth.auth?.let { - request.header("Authorization", Credentials.basic(it.user, it.pass)) - } - request.header("Depth", if (depth < 0) "infinity" else depth.toString()) - return HttpHelper.client.newCall(request.build()).await() + val url = httpUrl + val auth = HttpAuth.auth + if (url != null && auth != null) { + return kotlin.runCatching { + okHttpClient.newCall { + url(url) + addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) + // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性 + // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。 + val requestBody = requestPropsStr.toRequestBody("text/plain".toMediaType()) + method("PROPFIND", requestBody) + } + }.onFailure { + it.printStackTrace() + }.getOrNull() } return null } @@ -160,11 +148,15 @@ class WebDav(urlStr: String) { * @return 是否创建成功 */ suspend fun makeAsDir(): Boolean { - httpUrl?.let { url -> - val request = Request.Builder() - .url(url) - .method("MKCOL", null) - return execRequest(request) + val url = httpUrl + val auth = HttpAuth.auth + if (url != null && auth != null) { + okHttpClient.newCall { + url(url) + method("MKCOL", null) + addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) + } + return true } return false } @@ -198,11 +190,15 @@ class WebDav(urlStr: String) { if (!file.exists()) return false // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 val fileBody = file.asRequestBody(contentType?.toMediaType()) - httpUrl?.let { - val request = Request.Builder() - .url(it) - .put(fileBody) - return execRequest(request) + val url = httpUrl + val auth = HttpAuth.auth + if (url != null && auth != null) { + okHttpClient.newCall { + url(url) + put(fileBody) + addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) + } + return true } return false } @@ -210,37 +206,28 @@ class WebDav(urlStr: String) { suspend fun upload(byteArray: ByteArray, contentType: String? = null): Boolean { // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 val fileBody = byteArray.toRequestBody(contentType?.toMediaType()) - httpUrl?.let { - val request = Request.Builder() - .url(it) - .put(fileBody) - return execRequest(request) + val url = httpUrl + val auth = HttpAuth.auth + if (url != null && auth != null) { + okHttpClient.newCall { + url(url) + put(fileBody) + addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) + } + return true } return false } - /** - * 执行请求,获取响应结果 - * @param requestBuilder 因为还需要追加验证信息,所以此处传递Request.Builder的对象,而不是Request的对象 - * @return 请求执行的结果 - */ - @Throws(IOException::class) - private suspend fun execRequest(requestBuilder: Request.Builder): Boolean { - HttpAuth.auth?.let { - requestBuilder.header("Authorization", Credentials.basic(it.user, it.pass)) - } - val response = HttpHelper.client.newCall(requestBuilder.build()).await() - return response.isSuccessful - } - @Throws(IOException::class) private suspend fun getInputStream(): InputStream? { val url = httpUrl val auth = HttpAuth.auth if (url != null && auth != null) { - return RxHttp.get(url) - .addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) - .toInputStream().await() + return okHttpClient.newCall { + url(url) + addHeader("Authorization", Credentials.basic(auth.user, auth.pass)) + }.byteStream() } return null } diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index 318c1dcd3..73e90e782 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -16,9 +16,6 @@ import io.legado.app.help.CacheManager import io.legado.app.help.JsExtensions import io.legado.app.help.http.* import io.legado.app.utils.* -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toByteArray -import rxhttp.wrapper.param.toStrResponse import java.net.URLEncoder import java.util.* import java.util.regex.Pattern @@ -58,6 +55,7 @@ class AnalyzeUrl( private var charset: String? = null private var method = RequestMethod.GET private var proxy: String? = null + private var retry: Int = 0 init { baseUrl = baseUrl.split(splitUrlRegex, 1)[0] @@ -200,6 +198,7 @@ class AnalyzeUrl( option.js?.let { evalJS(it) } + retry = option.retry } } headerMap[UA_NAME] ?: let { @@ -305,62 +304,43 @@ class AnalyzeUrl( params.sourceRegex = sourceRegex params.postData = body?.toByteArray() params.tag = tag - return HttpHelper.ajax(params) + return getWebViewSrc(params) } - return when (method) { - RequestMethod.POST -> { - if (fieldMap.isNotEmpty() || body.isNullOrBlank()) { - RxHttp.postForm(url) - .setAssemblyEnabled(false) - .setOkClient(HttpHelper.getProxyClient(proxy)) - .addAllEncoded(fieldMap) - .addAllHeader(headerMap) - .toStrResponse().await() - } else { - RxHttp.postJson(url) - .setAssemblyEnabled(false) - .setOkClient(HttpHelper.getProxyClient(proxy)) - .addAll(body) - .addAllHeader(headerMap) - .toStrResponse().await() + return getProxyClient(proxy).newCallStrResponse(retry) { + removeHeader(UA_NAME) + addHeaders(headerMap) + when (method) { + RequestMethod.POST -> { + url(url) + if (fieldMap.isNotEmpty() || body.isNullOrBlank()) { + postForm(fieldMap, true) + } else { + postJson(body) + } } + else -> get(url, fieldMap, true) } - else -> RxHttp.get(url) - .setAssemblyEnabled(false) - .setOkClient(HttpHelper.getProxyClient(proxy)) - .addAllEncoded(fieldMap) - .addAllHeader(headerMap) - .toStrResponse().await() } } suspend fun getByteArray(tag: String? = null): ByteArray { setCookie(tag) - return when (method) { - RequestMethod.POST -> { - if (fieldMap.isNotEmpty() || body.isNullOrBlank()) { - RxHttp.postForm(url) - .setAssemblyEnabled(false) - .setOkClient(HttpHelper.getProxyClient(proxy)) - .addAllEncoded(fieldMap) - .addAllHeader(headerMap) - .toByteArray().await() - } else { - RxHttp.postJson(url) - .setAssemblyEnabled(false) - .setOkClient(HttpHelper.getProxyClient(proxy)) - .addAll(body) - .addAllHeader(headerMap) - .toByteArray().await() + @Suppress("BlockingMethodInNonBlockingContext") + return getProxyClient(proxy).newCall(retry) { + removeHeader(UA_NAME) + addHeaders(headerMap) + when (method) { + RequestMethod.POST -> { + url(url) + if (fieldMap.isNotEmpty() || body.isNullOrBlank()) { + postForm(fieldMap, true) + } else { + postJson(body) + } } + else -> get(url, fieldMap, true) } - else -> RxHttp.get(url) - .setAssemblyEnabled(false) - .setOkClient(HttpHelper.getProxyClient(proxy)) - .addAllEncoded(fieldMap) - .addAllHeader(headerMap) - .toByteArray().await() - } + }.bytes() } private fun setCookie(tag: String?) { @@ -393,7 +373,8 @@ class AnalyzeUrl( val headers: Any?, val body: Any?, val type: String?, - val js: String? + val js: String?, + val retry: Int = 0 ) } diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 8eef8d50b..e6984a548 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -13,6 +13,9 @@ import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.HtmlFormatter import io.legado.app.utils.NetworkUtils import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.async +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import splitties.init.appCtx @@ -45,11 +48,11 @@ object BookContent { val analyzeRule = AnalyzeRule(book).setContent(body, baseUrl) analyzeRule.setRedirectUrl(baseUrl) analyzeRule.nextChapterUrl = mNextChapterUrl + scope.ensureActive() var contentData = analyzeContent( book, baseUrl, redirectUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl ) content.append(contentData.content).append("\n") - if (contentData.nextUrl.size == 1) { var nextUrl = contentData.nextUrl[0] while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) { @@ -58,6 +61,7 @@ object BookContent { == NetworkUtils.getAbsoluteURL(baseUrl, mNextChapterUrl) ) break nextUrlList.add(nextUrl) + scope.ensureActive() val res = AnalyzeUrl( ruleUrl = nextUrl, book = book, @@ -75,29 +79,27 @@ object BookContent { } Debug.log(bookSource.bookSourceUrl, "◇本章总页数:${nextUrlList.size}") } else if (contentData.nextUrl.size > 1) { - val contentDataList = arrayListOf>() - for (item in contentData.nextUrl) { - if (!nextUrlList.contains(item)) - contentDataList.add(ContentData(nextUrl = item)) - } - for (item in contentDataList) { - withContext(scope.coroutineContext) { - val res = AnalyzeUrl( - ruleUrl = item.nextUrl, - book = book, - headerMapF = bookSource.getHeaderMap() - ).getStrResponse(bookSource.bookSourceUrl) - res.body?.let { nextBody -> - contentData = analyzeContent( - book, item.nextUrl, res.url, nextBody, contentRule, - bookChapter, bookSource, mNextChapterUrl, false + Debug.log(bookSource.bookSourceUrl, "◇并发解析目录,总页数:${contentData.nextUrl.size}") + withContext(IO) { + val asyncArray = Array(contentData.nextUrl.size) { + async(IO) { + val urlStr = contentData.nextUrl[it] + val analyzeUrl = AnalyzeUrl( + ruleUrl = urlStr, + book = book, + headerMapF = bookSource.getHeaderMap() ) - item.content = contentData.content + val res = analyzeUrl.getStrResponse(bookSource.bookSourceUrl) + analyzeContent( + book, urlStr, res.url, res.body!!, contentRule, + bookChapter, bookSource, mNextChapterUrl, false + ).content } } - } - for (item in contentDataList) { - content.append(item.content).append("\n") + asyncArray.forEach { coroutine -> + scope.ensureActive() + content.append(coroutine.await()).append("\n") + } } } content.deleteCharAt(content.length - 1) diff --git a/app/src/main/java/io/legado/app/ui/README.md b/app/src/main/java/io/legado/app/ui/README.md index bc6549bec..05d4be931 100644 --- a/app/src/main/java/io/legado/app/ui/README.md +++ b/app/src/main/java/io/legado/app/ui/README.md @@ -10,7 +10,7 @@ * book\source 书源界面 * book\changeCover 封面换源界面 * book\changeSource 换源界面 -* book\chapterList 目录界面 +* book\toc 目录界面 * book\download 下载界面 * book\explore 发现界面 * book\local 书籍导入界面 diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt index 67aec440f..8401830c6 100644 --- a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt @@ -1,14 +1,10 @@ package io.legado.app.ui.association -import android.content.Intent import android.os.Bundle import androidx.activity.viewModels import io.legado.app.base.VMBaseActivity import io.legado.app.constant.Theme import io.legado.app.databinding.ActivityTranslucenceBinding -import io.legado.app.ui.main.MainActivity -import io.legado.app.utils.startActivity - import io.legado.app.utils.toastOnUi class FileAssociationActivity : @@ -39,14 +35,4 @@ class FileAssociationActivity : } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - finish() - //返回后直接跳转到主页面 - gotoMainActivity() - } - - private fun gotoMainActivity() { - startActivity() - } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt index 7a08cacb1..a67c7e7f8 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt @@ -9,13 +9,14 @@ import io.legado.app.data.appDb import io.legado.app.data.entities.BookSource import io.legado.app.help.AppConfig import io.legado.app.help.SourceHelp +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import io.legado.app.help.storage.OldRule import io.legado.app.help.storage.Restore import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonObject -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toText class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) { var groupName: String? = null @@ -111,7 +112,9 @@ class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) { } private suspend fun importSourceUrl(url: String) { - RxHttp.get(url).toText("utf-8").await().let { body -> + okHttpClient.newCall { + url(url) + }.text("utf-8").let { body -> val items: List> = Restore.jsonPath.parse(body).read("$") for (item in items) { val jsonItem = Restore.jsonPath.parse(item) diff --git a/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt index a9a937114..8d6da537e 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt @@ -4,10 +4,11 @@ import android.app.Application import androidx.lifecycle.MutableLiveData import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.ReplaceRule +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import io.legado.app.help.storage.OldReplace import io.legado.app.utils.isAbsUrl -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toText class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) { val errorLiveData = MutableLiveData() @@ -18,7 +19,9 @@ class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) { fun import(text: String) { execute { if (text.isAbsUrl()) { - RxHttp.get(text).toText("utf-8").await().let { + okHttpClient.newCall { + url(text) + }.text("utf-8").let { val rules = OldReplace.jsonToReplaceRules(it) allRules.addAll(rules) } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt index 0db5f7af0..0658d5b5a 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt @@ -9,10 +9,11 @@ import io.legado.app.data.appDb import io.legado.app.data.entities.RssSource import io.legado.app.help.AppConfig import io.legado.app.help.SourceHelp +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import io.legado.app.help.storage.Restore import io.legado.app.utils.* -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toText class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) { var groupName: String? = null @@ -106,7 +107,9 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) { } private suspend fun importSourceUrl(url: String) { - RxHttp.get(url).toText("utf-8").await().let { body -> + okHttpClient.newCall { + url(url) + }.text("utf-8").let { body -> val items: List> = Restore.jsonPath.parse(body).read("$") for (item in items) { val jsonItem = Restore.jsonPath.parse(item) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 159cced9d..2e616ab44 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -80,6 +80,7 @@ class ReadBookActivity : ReadBookBaseActivity(), } private val sourceEditActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + it ?: return@registerForActivityResult if (it.resultCode == RESULT_OK) { viewModel.upBookSource { upView() @@ -88,12 +89,14 @@ class ReadBookActivity : ReadBookBaseActivity(), } private val replaceActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + it ?: return@registerForActivityResult if (it.resultCode == RESULT_OK) { viewModel.replaceRuleChanged() } } private val searchContentActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + it ?: return@registerForActivityResult it.data?.let { data -> data.getIntExtra("index", ReadBook.durChapterIndex).let { index -> viewModel.searchContentQuery = data.getStringExtra("query") ?: "" diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 1a89cd4b1..170c51ab4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -15,8 +15,12 @@ import io.legado.app.constant.EventBus import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.databinding.DialogReadBgTextBinding import io.legado.app.databinding.ItemBgImageBinding +import io.legado.app.help.DefaultData import io.legado.app.help.ReadBookConfig +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.selector import io.legado.app.lib.permission.Permissions import io.legado.app.lib.permission.PermissionsCompat import io.legado.app.lib.theme.bottomBackground @@ -27,8 +31,6 @@ import io.legado.app.ui.document.FilePicker import io.legado.app.ui.document.FilePickerParam import io.legado.app.utils.* import io.legado.app.utils.viewbindingdelegate.viewBinding -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toByteArray import java.io.File class BgTextConfigDialog : BaseDialogFragment() { @@ -96,25 +98,23 @@ class BgTextConfigDialog : BaseDialogFragment() { (activity as ReadBookActivity).bottomDialog-- } - private fun initView() { + private fun initView() = with(binding) { val bg = requireContext().bottomBackground val isLight = ColorUtils.isColorLight(bg) primaryTextColor = requireContext().getPrimaryTextColor(isLight) secondaryTextColor = requireContext().getSecondaryTextColor(isLight) - binding.rootView.setBackgroundColor(bg) - binding.swDarkStatusIcon.setTextColor(primaryTextColor) - binding.ivImport.setColorFilter(primaryTextColor) - binding.ivExport.setColorFilter(primaryTextColor) - binding.ivDelete.setColorFilter(primaryTextColor) - binding.tvBgImage.setTextColor(primaryTextColor) - } - - @SuppressLint("InflateParams") - private fun initData() = with(ReadBookConfig.durConfig) { - binding.tvName.text = name.ifBlank { "文字" } - binding.swDarkStatusIcon.isChecked = curStatusIconDark() + rootView.setBackgroundColor(bg) + tvNameTitle.setTextColor(primaryTextColor) + tvName.setTextColor(secondaryTextColor) + ivEdit.setColorFilter(secondaryTextColor) + tvRestore.setTextColor(primaryTextColor) + swDarkStatusIcon.setTextColor(primaryTextColor) + ivImport.setColorFilter(primaryTextColor) + ivExport.setColorFilter(primaryTextColor) + ivDelete.setColorFilter(primaryTextColor) + tvBgImage.setTextColor(primaryTextColor) adapter = BgAdapter(requireContext(), secondaryTextColor) - binding.recyclerView.adapter = adapter + recyclerView.adapter = adapter adapter.addHeaderView { ItemBgImageBinding.inflate(layoutInflater, it, false).apply { tvName.setTextColor(secondaryTextColor) @@ -131,6 +131,12 @@ class BgTextConfigDialog : BaseDialogFragment() { } } + @SuppressLint("InflateParams") + private fun initData() = with(ReadBookConfig.durConfig) { + binding.tvName.text = name.ifBlank { "文字" } + binding.swDarkStatusIcon.isChecked = curStatusIconDark() + } + @SuppressLint("InflateParams") private fun initEvent() = with(ReadBookConfig.durConfig) { binding.ivEdit.setOnClickListener { @@ -148,6 +154,17 @@ class BgTextConfigDialog : BaseDialogFragment() { cancelButton() }.show() } + binding.tvRestore.setOnClickListener { + val defaultConfigs = DefaultData.readConfigs + val layoutNames = defaultConfigs.map { it.name } + selector("选择预设布局", layoutNames) { _, i -> + if (i >= 0) { + ReadBookConfig.durConfig = defaultConfigs[i] + initData() + postEvent(EventBus.UP_CONFIG, true) + } + } + } binding.swDarkStatusIcon.setOnCheckedChangeListener { _, isChecked -> setCurStatusIconDark(isChecked) (activity as? ReadBookActivity)?.upSystemUiVisibility() @@ -291,7 +308,9 @@ class BgTextConfigDialog : BaseDialogFragment() { private fun importNetConfig(url: String) { execute { - RxHttp.get(url).toByteArray().await().let { + okHttpClient.newCall { + url(url) + }.bytes().let { importConfig(it) } }.onError { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt index c7eed9534..89246859e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt @@ -6,9 +6,10 @@ import io.legado.app.base.BaseViewModel import io.legado.app.data.appDb import io.legado.app.data.entities.HttpTTS import io.legado.app.help.DefaultData +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import io.legado.app.utils.* -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toText class SpeakEngineViewModel(application: Application) : BaseViewModel(application) { @@ -20,7 +21,9 @@ class SpeakEngineViewModel(application: Application) : BaseViewModel(application fun importOnLine(url: String) { execute { - RxHttp.get(url).toText("utf-8").await().let { json -> + okHttpClient.newCall { + url(url) + }.text("utf-8").let { json -> import(json) } }.onSuccess { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt index 6494cf762..d1ce1ae26 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt @@ -5,10 +5,11 @@ import io.legado.app.base.BaseViewModel import io.legado.app.data.appDb import io.legado.app.data.entities.TxtTocRule import io.legado.app.help.DefaultData +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonArray -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toText class TocRegexViewModel(application: Application) : BaseViewModel(application) { @@ -29,7 +30,9 @@ class TocRegexViewModel(application: Application) : BaseViewModel(application) { fun importOnLine(url: String, finally: (msg: String) -> Unit) { execute { - RxHttp.get(url).toText("utf-8").await().let { json -> + okHttpClient.newCall { + url(url) + }.text("utf-8").let { json -> GSON.fromJsonArray(json)?.let { appDb.txtTocRuleDao.insert(*it.toTypedArray()) } diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index 2a28a1b7f..2a5e414ee 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -28,7 +28,6 @@ import io.legado.app.lib.permission.PermissionsCompat import io.legado.app.lib.theme.ATH import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.ui.widget.prefs.ColorPreference -import io.legado.app.ui.widget.prefs.PreferenceCategory import io.legado.app.utils.* import java.io.File @@ -55,15 +54,8 @@ class ThemeConfigFragment : BasePreferenceFragment(), if (Build.VERSION.SDK_INT < 26) { preferenceScreen.removePreferenceRecursively(PreferKey.launcherIcon) } - if (AppConfig.isGooglePlay) { - upPreferenceSummary(PreferKey.bgImage, getPrefString(PreferKey.bgImage)) - upPreferenceSummary(PreferKey.bgImageN, getPrefString(PreferKey.bgImageN)) - } else { - findPreference("dayThemeCategory") - ?.removePreferenceRecursively(PreferKey.bgImage) - findPreference("nightThemeCategory") - ?.removePreferenceRecursively(PreferKey.bgImageN) - } + upPreferenceSummary(PreferKey.bgImage, getPrefString(PreferKey.bgImage)) + upPreferenceSummary(PreferKey.bgImageN, getPrefString(PreferKey.bgImageN)) upPreferenceSummary(PreferKey.barElevation, AppConfig.elevation.toString()) findPreference(PreferKey.cBackground)?.let { it.onSaveColor = { color -> diff --git a/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt b/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt index c3f601271..17a11d799 100644 --- a/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt +++ b/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt @@ -23,6 +23,7 @@ class FilePickerActivity : private val selectDocTree = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { + it ?: return@registerForActivityResult if (it.isContentScheme()) { contentResolver.takePersistableUriPermission( it, @@ -33,6 +34,7 @@ class FilePickerActivity : } private val selectDoc = registerForActivityResult(ActivityResultContracts.OpenDocument()) { + it ?: return@registerForActivityResult onResult(Intent().setData(it)) } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 87f048fda..ac9f91190 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -7,13 +7,14 @@ import io.legado.app.data.appDb import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookSource +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.text import io.legado.app.model.webBook.PreciseSearch import io.legado.app.model.webBook.WebBook import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.isActive -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toText class BookshelfViewModel(application: Application) : BaseViewModel(application) { @@ -87,7 +88,9 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application) val text = str.trim() when { text.isAbsUrl() -> { - RxHttp.get(text).toText().await().let { + okHttpClient.newCall { + url(text) + }.text().let { importBookshelf(it, groupId) } } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt index 221b296ae..8bdd5faa9 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt @@ -16,6 +16,7 @@ import io.legado.app.data.entities.RssArticle import io.legado.app.databinding.FragmentRssArticlesBinding import io.legado.app.databinding.ViewLoadMoreBinding import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.accentColor import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.VerticalDivider @@ -50,18 +51,18 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { viewModel.init(arguments) initView() - binding.refreshRecyclerView.startLoading() initView() initData() } private fun initView() = with(binding) { - ATH.applyEdgeEffectColor(refreshRecyclerView.recyclerView) - refreshRecyclerView.recyclerView.layoutManager = if (activityViewModel.isGridLayout) { - refreshRecyclerView.recyclerView.setPadding(8, 0, 8, 0) + refreshLayout.setColorSchemeColors(accentColor) + ATH.applyEdgeEffectColor(recyclerView) + recyclerView.layoutManager = if (activityViewModel.isGridLayout) { + recyclerView.setPadding(8, 0, 8, 0) GridLayoutManager(requireContext(), 2) } else { - refreshRecyclerView.recyclerView.addItemDecoration(VerticalDivider(requireContext())) + recyclerView.addItemDecoration(VerticalDivider(requireContext())) LinearLayoutManager(requireContext()) } @@ -70,18 +71,15 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme 2 -> RssArticlesAdapter2(requireContext(), this@RssArticlesFragment) else -> RssArticlesAdapter(requireContext(), this@RssArticlesFragment) } - refreshRecyclerView.recyclerView.adapter = adapter + recyclerView.adapter = adapter loadMoreView = LoadMoreView(requireContext()) adapter.addFooterView { ViewLoadMoreBinding.bind(loadMoreView) } - refreshRecyclerView.onRefreshStart = { - activityViewModel.rssSource?.let { - viewModel.loadContent(it) - } + refreshLayout.setOnRefreshListener { + loadArticles() } - refreshRecyclerView.recyclerView.addOnScrollListener(object : - RecyclerView.OnScrollListener() { + recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (!recyclerView.canScrollVertically(1)) { @@ -89,6 +87,10 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme } } }) + refreshLayout.post { + refreshLayout.isRefreshing = true + loadArticles() + } } private fun initData() { @@ -101,6 +103,12 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme } } + private fun loadArticles() { + activityViewModel.rssSource?.let { + viewModel.loadContent(it) + } + } + private fun scrollToBottom() { if (viewModel.isLoading) return if (loadMoreView.hasMore && adapter.getActualItemCount() > 0) { @@ -113,7 +121,7 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme override fun observeLiveBus() { viewModel.loadFinally.observe(viewLifecycleOwner) { - binding.refreshRecyclerView.stopLoading() + binding.refreshLayout.isRefreshing = false if (it) { loadMoreView.startLoad() } else { diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt index 403a06d2e..e317ae734 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt @@ -28,7 +28,7 @@ class RssFavoritesActivity : BaseActivity(), } private fun initView() { - binding.refreshRecyclerView.recyclerView.let { + binding.recyclerView.let { it.layoutManager = LinearLayoutManager(this) it.addItemDecoration(VerticalDivider(this)) adapter = RssFavoritesAdapter(this, this) diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 05789e00a..95aaf8325 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -37,7 +37,6 @@ class ReadRssActivity : VMBaseActivity override val viewModel: ReadRssViewModel by viewModels() - private val savePathRequestCode = 132 private val imagePathKey = "" private var starMenuItem: MenuItem? = null private var ttsMenuItem: MenuItem? = null diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index a2faacfad..594a4a543 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -16,6 +16,8 @@ import io.legado.app.data.appDb import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssStar +import io.legado.app.help.http.newCall +import io.legado.app.help.http.okHttpClient import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.rss.Rss import io.legado.app.utils.DocumentUtils @@ -24,8 +26,6 @@ import io.legado.app.utils.isContentScheme import io.legado.app.utils.writeBytes import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch -import rxhttp.wrapper.param.RxHttp -import rxhttp.wrapper.param.toByteArray import java.io.File import java.util.* @@ -147,7 +147,9 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), private suspend fun webData2bitmap(data: String): ByteArray? { return if (URLUtil.isValidUrl(data)) { - RxHttp.get(data).toByteArray().await() + okHttpClient.newCall { + url(data) + }.bytes() } else { Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT) } diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/RefreshRecyclerView.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/RefreshRecyclerView.kt deleted file mode 100644 index 81f3db242..000000000 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/RefreshRecyclerView.kt +++ /dev/null @@ -1,92 +0,0 @@ -package io.legado.app.ui.widget.recycler - -import android.annotation.SuppressLint -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.widget.LinearLayout -import androidx.recyclerview.widget.LinearLayoutManager -import io.legado.app.R -import io.legado.app.databinding.ViewRefreshRecyclerBinding -import io.legado.app.lib.theme.ATH - - -@SuppressLint("ClickableViewAccessibility") -class RefreshRecyclerView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) { - private var binding: ViewRefreshRecyclerBinding - private var durTouchX = -1000000f - private var durTouchY = -1000000f - - var onRefreshStart: (() -> Unit)? = null - - init { - orientation = VERTICAL - val view = LayoutInflater.from(context).inflate(R.layout.view_refresh_recycler, this, true) - binding = ViewRefreshRecyclerBinding.bind(view) - ATH.applyEdgeEffectColor(binding.recyclerView) - binding.recyclerView.setOnTouchListener(object : OnTouchListener { - @SuppressLint("ClickableViewAccessibility") - override fun onTouch(v: View?, event: MotionEvent?): Boolean { - when (event?.action) { - MotionEvent.ACTION_DOWN -> { - durTouchX = event.x - durTouchY = event.y - } - MotionEvent.ACTION_MOVE -> { - if (durTouchX == -1000000f) { - durTouchX = event.x - } - if (durTouchY == -1000000f) - durTouchY = event.y - - val dY = event.y - durTouchY //>0下拉 - durTouchY = event.y - if (!binding.refreshProgressBar.isAutoLoading && - binding.refreshProgressBar.getSecondDurProgress() == binding.refreshProgressBar.secondFinalProgress - ) { - binding.recyclerView.adapter?.let { - if (it.itemCount > 0) { - if (0 == (binding.recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()) { - binding.refreshProgressBar.setSecondDurProgress((binding.refreshProgressBar.getSecondDurProgress() + dY / 2).toInt()) - } - } else { - binding.refreshProgressBar.setSecondDurProgress((binding.refreshProgressBar.getSecondDurProgress() + dY / 2).toInt()) - } - } - return binding.refreshProgressBar.getSecondDurProgress() > 0 - } - } - MotionEvent.ACTION_UP -> { - if (!binding.refreshProgressBar.isAutoLoading && - binding.refreshProgressBar.secondMaxProgress > 0 && - binding.refreshProgressBar.getSecondDurProgress() > 0 - ) { - if (binding.refreshProgressBar.getSecondDurProgress() >= binding.refreshProgressBar.secondMaxProgress) { - binding.refreshProgressBar.isAutoLoading = true - onRefreshStart?.invoke() - } else { - binding.refreshProgressBar.setSecondDurProgressWithAnim(0) - } - } - durTouchX = -1000000f - durTouchY = -1000000f - } - } - return false - } - }) - } - - val recyclerView get() = binding.recyclerView - - fun startLoading() { - binding.refreshProgressBar.isAutoLoading = true - onRefreshStart?.invoke() - } - - fun stopLoading() { - binding.refreshProgressBar.isAutoLoading = false - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/OkHttpExtensions.kt b/app/src/main/java/io/legado/app/utils/OkHttpExtensions.kt deleted file mode 100644 index b21bcc8e4..000000000 --- a/app/src/main/java/io/legado/app/utils/OkHttpExtensions.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.legado.app.utils - -import kotlinx.coroutines.suspendCancellableCoroutine -import okhttp3.Call -import okhttp3.Callback -import okhttp3.Response -import java.io.IOException -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -suspend fun Call.await(): Response = suspendCancellableCoroutine { block -> - - block.invokeOnCancellation { - cancel() - } - - enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - block.resumeWithException(e) - } - - override fun onResponse(call: Call, response: Response) { - block.resume(response) - } - }) - -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_favorites.xml b/app/src/main/res/layout/activity_rss_favorites.xml index 7740c1981..098af211a 100644 --- a/app/src/main/res/layout/activity_rss_favorites.xml +++ b/app/src/main/res/layout/activity_rss_favorites.xml @@ -1,7 +1,6 @@ @@ -13,13 +12,19 @@ app:title="@string/favorites" app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintVertical_bias="0.0"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_read_bg_text.xml b/app/src/main/res/layout/dialog_read_bg_text.xml index 553f893b4..175bb68c0 100644 --- a/app/src/main/res/layout/dialog_read_bg_text.xml +++ b/app/src/main/res/layout/dialog_read_bg_text.xml @@ -16,6 +16,7 @@ android:gravity="center_vertical"> + + + + - + android:layout_height="match_parent"> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 29e200c8c..cab096ed7 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -820,5 +820,7 @@ 正文源码 列表源码 此url已订阅 + 高刷 + 使用屏幕最高刷新率 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c04585a7d..99f5030d0 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -812,6 +812,8 @@ 匯出到WebDav 反轉內容 除錯 + 當機日誌 + 使用自訂中文分行 圖片樣式 系統TTS 匯出格式 @@ -822,5 +824,7 @@ 正文原始碼 列表原始碼 此url已訂閱 + 高刷 + 使用螢幕最高刷新率 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 05aa55b5a..240704cac 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -824,5 +824,7 @@ 正文源码 列表源码 此url已订阅 + 高刷 + 使用屏幕最高刷新率 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04b53023e..bd230bc1f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -397,7 +397,6 @@ 分类Url 登录URL(loginUrl) 源注释(sourceComment) - 请求头(header) 搜索地址(url) 发现地址规则(url) 书籍列表规则(bookList) @@ -455,9 +454,10 @@ Data parsing failed - + HTTP Header Debug source Import from QR code + Share selected sources Scan QR code Tap to display Menu when selected Theme @@ -721,7 +721,6 @@ Select a legacy backup folder Enabled Disabled - Starting download This book is already in Download list click to open @@ -744,7 +743,6 @@ Enable replace rule by default For new added books Select restore file - Switch default theme Day background can not be too dark! Day bottom can not be too dark! Night background can not be too bright! @@ -771,13 +769,13 @@ Save night theme config Theme list Save, Import, Share theme - Share selected sources + Switch default theme Sort by update time Search content - 关注公众号[开源阅读]获取订阅源! + Follow [开源阅读] at WeChat for more RSS source! 当前没有发现源,关注公众号[开源阅读]添加带发现的书源! 将焦点放到输入框按下物理按键会自动录入键值,多个按键会自动用英文逗号隔开. - 主题名称 + Theme name 自动清除过期搜索数据 超过一天的搜索数据 重新分段 @@ -786,13 +784,13 @@ 智能扫描 拷贝书籍URL 拷贝目录URL - 没有书籍 - 保留原名 + NO BOOK + Keep original name 点击区域设置 - 关闭 - 下一页 - 上一页 - 无操作 + Close + Next Page + Prev Page + No Action 正文标题 显示/隐藏 footer header @@ -827,5 +825,7 @@ 正文源码 列表源码 此url已订阅 + 高刷 + 使用屏幕最高刷新率 diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index c15aee51e..66a9cc4fa 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -55,6 +55,12 @@ android:key="userAgent" android:title="UserAgent" /> + +