parent
be25d5569b
commit
b7fc0b98f4
Binary file not shown.
@ -0,0 +1,471 @@ |
||||
/* |
||||
Highlight.js 10.5.0 (af20048d) |
||||
License: BSD-3-Clause |
||||
Copyright (c) 2006-2020, Ivan Sagalaev |
||||
*/ |
||||
var hljs=function(){"use strict";function e(t){ |
||||
return t instanceof Map?t.clear=t.delete=t.set=()=>{ |
||||
throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{ |
||||
throw Error("set is read-only") |
||||
}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{var s=t[n] |
||||
;"object"!=typeof s||Object.isFrozen(s)||e(s)})),t}var t=e,n=e;t.default=n |
||||
;class s{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data} |
||||
ignoreMatch(){this.ignore=!0}}function r(e){ |
||||
return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'") |
||||
}function a(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] |
||||
;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const i=e=>!!e.kind |
||||
;class o{constructor(e,t){ |
||||
this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ |
||||
this.buffer+=r(e)}openNode(e){if(!i(e))return;let t=e.kind |
||||
;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){ |
||||
i(e)&&(this.buffer+="</span>")}value(){return this.buffer}span(e){ |
||||
this.buffer+=`<span class="${e}">`}}class l{constructor(){this.rootNode={ |
||||
children:[]},this.stack=[this.rootNode]}get top(){ |
||||
return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ |
||||
this.top.children.push(e)}openNode(e){const t={kind:e,children:[]} |
||||
;this.add(t),this.stack.push(t)}closeNode(){ |
||||
if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ |
||||
for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} |
||||
walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ |
||||
return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), |
||||
t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ |
||||
"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ |
||||
l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e} |
||||
addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} |
||||
addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root |
||||
;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){ |
||||
return new o(this,this.options).value()}finalize(){return!0}}function u(e){ |
||||
return e?"string"==typeof e?e:e.source:null} |
||||
const g="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",h="\\b\\d+(\\.\\d+)?",f="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",p="\\b(0b[01]+)",m={ |
||||
begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'", |
||||
illegal:"\\n",contains:[m]},x={className:"string",begin:'"',end:'"', |
||||
illegal:"\\n",contains:[m]},E={ |
||||
begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ |
||||
},v=(e,t,n={})=>{const s=a({className:"comment",begin:e,end:t,contains:[]},n) |
||||
;return s.contains.push(E),s.contains.push({className:"doctag", |
||||
begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),s |
||||
},N=v("//","$"),w=v("/\\*","\\*/"),R=v("#","$");var y=Object.freeze({ |
||||
__proto__:null,IDENT_RE:g,UNDERSCORE_IDENT_RE:d,NUMBER_RE:h,C_NUMBER_RE:f, |
||||
BINARY_NUMBER_RE:p, |
||||
RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", |
||||
SHEBANG:(e={})=>{const t=/^#![ ]*\// |
||||
;return e.binary&&(e.begin=((...e)=>e.map((e=>u(e))).join(""))(t,/.*\b/,e.binary,/\b.*/)), |
||||
a({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{ |
||||
0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:m,APOS_STRING_MODE:b, |
||||
QUOTE_STRING_MODE:x,PHRASAL_WORDS_MODE:E,COMMENT:v,C_LINE_COMMENT_MODE:N, |
||||
C_BLOCK_COMMENT_MODE:w,HASH_COMMENT_MODE:R,NUMBER_MODE:{className:"number", |
||||
begin:h,relevance:0},C_NUMBER_MODE:{className:"number",begin:f,relevance:0}, |
||||
BINARY_NUMBER_MODE:{className:"number",begin:p,relevance:0},CSS_NUMBER_MODE:{ |
||||
className:"number", |
||||
begin:h+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", |
||||
relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp", |
||||
begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[m,{begin:/\[/,end:/\]/, |
||||
relevance:0,contains:[m]}]}]},TITLE_MODE:{className:"title",begin:g,relevance:0 |
||||
},UNDERSCORE_TITLE_MODE:{className:"title",begin:d,relevance:0},METHOD_GUARD:{ |
||||
begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ |
||||
"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ |
||||
t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function _(e,t){ |
||||
"."===e.input[e.index-1]&&t.ignoreMatch()}function k(e,t){ |
||||
t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", |
||||
e.__beforeBegin=_,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords) |
||||
}function M(e,t){ |
||||
Array.isArray(e.illegal)&&(e.illegal=((...e)=>"("+e.map((e=>u(e))).join("|")+")")(...e.illegal)) |
||||
}function O(e,t){if(e.match){ |
||||
if(e.begin||e.end)throw Error("begin & end are not supported with match") |
||||
;e.begin=e.match,delete e.match}}function A(e,t){ |
||||
void 0===e.relevance&&(e.relevance=1)} |
||||
const L=["of","and","for","in","not","or","if","then","parent","list","value"] |
||||
;function B(e,t){return t?Number(t):(e=>L.includes(e.toLowerCase()))(e)?0:1} |
||||
function I(e,{plugins:t}){function n(t,n){ |
||||
return RegExp(u(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class s{ |
||||
constructor(){ |
||||
this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} |
||||
addRule(e,t){ |
||||
t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), |
||||
this.matchAt+=(e=>RegExp(e.toString()+"|").exec("").length-1)(e)+1}compile(){ |
||||
0===this.regexes.length&&(this.exec=()=>null) |
||||
;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(((e,t="|")=>{ |
||||
const n=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;let s=0,r="" |
||||
;for(let a=0;a<e.length;a++){s+=1;const i=s;let o=u(e[a]) |
||||
;for(a>0&&(r+=t),r+="(";o.length>0;){const e=n.exec(o);if(null==e){r+=o;break} |
||||
r+=o.substring(0,e.index), |
||||
o=o.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+i):(r+=e[0], |
||||
"("===e[0]&&s++)}r+=")"}return r})(e),!0),this.lastIndex=0}exec(e){ |
||||
this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e) |
||||
;if(!t)return null |
||||
;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),s=this.matchIndexes[n] |
||||
;return t.splice(0,n),Object.assign(t,s)}}class r{constructor(){ |
||||
this.rules=[],this.multiRegexes=[], |
||||
this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ |
||||
if(this.multiRegexes[e])return this.multiRegexes[e];const t=new s |
||||
;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), |
||||
t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ |
||||
return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ |
||||
this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ |
||||
const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex |
||||
;let n=t.exec(e) |
||||
;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ |
||||
const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} |
||||
return n&&(this.regexIndex+=n.position+1, |
||||
this.regexIndex===this.count&&this.considerAll()),n}} |
||||
if(e.compilerExtensions||(e.compilerExtensions=[]), |
||||
e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") |
||||
;return e.classNameAliases=a(e.classNameAliases||{}),function t(s,i){const o=s |
||||
;if(s.compiled)return o |
||||
;[O].forEach((e=>e(s,i))),e.compilerExtensions.forEach((e=>e(s,i))), |
||||
s.__beforeBegin=null,[k,M,A].forEach((e=>e(s,i))),s.compiled=!0;let l=null |
||||
;if("object"==typeof s.keywords&&(l=s.keywords.$pattern, |
||||
delete s.keywords.$pattern),s.keywords&&(s.keywords=((e,t)=>{const n={} |
||||
;return"string"==typeof e?s("keyword",e):Object.keys(e).forEach((t=>{s(t,e[t]) |
||||
})),n;function s(e,s){t&&(s=s.toLowerCase()),s.split(" ").forEach((t=>{ |
||||
const s=t.split("|");n[s[0]]=[e,B(s[0],s[1])]}))} |
||||
})(s.keywords,e.case_insensitive)), |
||||
s.lexemes&&l)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ") |
||||
;return l=l||s.lexemes||/\w+/, |
||||
o.keywordPatternRe=n(l,!0),i&&(s.begin||(s.begin=/\B|\b/), |
||||
o.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin), |
||||
s.end||s.endsWithParent||(s.end=/\B|\b/), |
||||
s.end&&(o.endRe=n(s.end)),o.terminatorEnd=u(s.end)||"", |
||||
s.endsWithParent&&i.terminatorEnd&&(o.terminatorEnd+=(s.end?"|":"")+i.terminatorEnd)), |
||||
s.illegal&&(o.illegalRe=n(s.illegal)), |
||||
s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>a(e,{ |
||||
variants:null},t)))),e.cachedVariants?e.cachedVariants:T(e)?a(e,{ |
||||
starts:e.starts?a(e.starts):null |
||||
}):Object.isFrozen(e)?a(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{t(e,o) |
||||
})),s.starts&&t(s.starts,i),o.matcher=(e=>{const t=new r |
||||
;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" |
||||
}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" |
||||
}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(o),o}(e)}function T(e){ |
||||
return!!e&&(e.endsWithParent||T(e.starts))}function j(e){const t={ |
||||
props:["language","code","autodetect"],data:()=>({detectedLanguage:"", |
||||
unknownLanguage:!1}),computed:{className(){ |
||||
return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){ |
||||
if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`), |
||||
this.unknownLanguage=!0,r(this.code);let t={} |
||||
;return this.autoDetect?(t=e.highlightAuto(this.code), |
||||
this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals), |
||||
this.detectedLanguage=this.language),t.value},autoDetect(){ |
||||
return!(this.language&&(e=this.autodetect,!e&&""!==e));var e}, |
||||
ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{ |
||||
class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{ |
||||
Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const S={ |
||||
"after:highlightBlock":({block:e,result:t,text:n})=>{const s=D(e) |
||||
;if(!s.length)return;const a=document.createElement("div") |
||||
;a.innerHTML=t.value,t.value=((e,t,n)=>{let s=0,a="";const i=[];function o(){ |
||||
return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset<t[0].offset?e:t:"start"===t[0].event?e:t:e.length?e:t |
||||
}function l(e){a+="<"+P(e)+[].map.call(e.attributes,(function(e){ |
||||
return" "+e.nodeName+'="'+r(e.value)+'"'})).join("")+">"}function c(e){ |
||||
a+="</"+P(e)+">"}function u(e){("start"===e.event?l:c)(e.node)} |
||||
for(;e.length||t.length;){let t=o() |
||||
;if(a+=r(n.substring(s,t[0].offset)),s=t[0].offset,t===e){i.reverse().forEach(c) |
||||
;do{u(t.splice(0,1)[0]),t=o()}while(t===e&&t.length&&t[0].offset===s) |
||||
;i.reverse().forEach(l) |
||||
}else"start"===t[0].event?i.push(t[0].node):i.pop(),u(t.splice(0,1)[0])} |
||||
return a+r(n.substr(s))})(s,D(a),n)}};function P(e){ |
||||
return e.nodeName.toLowerCase()}function D(e){const t=[];return function e(n,s){ |
||||
for(let r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?s+=r.nodeValue.length:1===r.nodeType&&(t.push({ |
||||
event:"start",offset:s,node:r}),s=e(r,s),P(r).match(/br|hr|img|input/)||t.push({ |
||||
event:"stop",offset:s,node:r}));return s}(e,0),t}const C=e=>{console.error(e) |
||||
},H=(e,...t)=>{console.log("WARN: "+e,...t)},$=(e,t)=>{ |
||||
console.log(`Deprecated as of ${e}. ${t}`)},U=r,z=a,K=Symbol("nomatch") |
||||
;return(e=>{const n=Object.create(null),r=Object.create(null),a=[];let i=!0 |
||||
;const o=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",u={ |
||||
disableAutodetect:!0,name:"Plain text",contains:[]};let g={ |
||||
noHighlightRe:/^(no-?highlight)$/i, |
||||
languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", |
||||
tabReplace:null,useBR:!1,languages:null,__emitter:c};function d(e){ |
||||
return g.noHighlightRe.test(e)}function h(e,t,n,s){const r={code:t,language:e} |
||||
;_("before:highlight",r);const a=r.result?r.result:f(r.language,r.code,n,s) |
||||
;return a.code=r.code,_("after:highlight",a),a}function f(e,t,r,o){const c=t |
||||
;function u(e,t){const n=w.case_insensitive?t[0].toLowerCase():t[0] |
||||
;return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]} |
||||
function d(){null!=_.subLanguage?(()=>{if(""===O)return;let e=null |
||||
;if("string"==typeof _.subLanguage){ |
||||
if(!n[_.subLanguage])return void M.addText(O) |
||||
;e=f(_.subLanguage,O,!0,k[_.subLanguage]),k[_.subLanguage]=e.top |
||||
}else e=p(O,_.subLanguage.length?_.subLanguage:null) |
||||
;_.relevance>0&&(A+=e.relevance),M.addSublanguage(e.emitter,e.language) |
||||
})():(()=>{if(!_.keywords)return void M.addText(O);let e=0 |
||||
;_.keywordPatternRe.lastIndex=0;let t=_.keywordPatternRe.exec(O),n="";for(;t;){ |
||||
n+=O.substring(e,t.index);const s=u(_,t);if(s){const[e,r]=s |
||||
;M.addText(n),n="",A+=r;const a=w.classNameAliases[e]||e;M.addKeyword(t[0],a) |
||||
}else n+=t[0];e=_.keywordPatternRe.lastIndex,t=_.keywordPatternRe.exec(O)} |
||||
n+=O.substr(e),M.addText(n)})(),O=""}function h(e){ |
||||
return e.className&&M.openNode(w.classNameAliases[e.className]||e.className), |
||||
_=Object.create(e,{parent:{value:_}}),_}function m(e,t,n){let r=((e,t)=>{ |
||||
const n=e&&e.exec(t);return n&&0===n.index})(e.endRe,n);if(r){if(e["on:end"]){ |
||||
const n=new s(e);e["on:end"](t,n),n.ignore&&(r=!1)}if(r){ |
||||
for(;e.endsParent&&e.parent;)e=e.parent;return e}} |
||||
if(e.endsWithParent)return m(e.parent,t,n)}function b(e){ |
||||
return 0===_.matcher.regexIndex?(O+=e[0],1):(T=!0,0)}function x(e){ |
||||
const t=e[0],n=c.substr(e.index),s=m(_,e,n);if(!s)return K;const r=_ |
||||
;r.skip?O+=t:(r.returnEnd||r.excludeEnd||(O+=t),d(),r.excludeEnd&&(O=t));do{ |
||||
_.className&&M.closeNode(),_.skip||_.subLanguage||(A+=_.relevance),_=_.parent |
||||
}while(_!==s.parent) |
||||
;return s.starts&&(s.endSameAsBegin&&(s.starts.endRe=s.endRe), |
||||
h(s.starts)),r.returnEnd?0:t.length}let E={};function v(t,n){const a=n&&n[0] |
||||
;if(O+=t,null==a)return d(),0 |
||||
;if("begin"===E.type&&"end"===n.type&&E.index===n.index&&""===a){ |
||||
if(O+=c.slice(n.index,n.index+1),!i){const t=Error("0 width match regex") |
||||
;throw t.languageName=e,t.badRule=E.rule,t}return 1} |
||||
if(E=n,"begin"===n.type)return function(e){ |
||||
const t=e[0],n=e.rule,r=new s(n),a=[n.__beforeBegin,n["on:begin"]] |
||||
;for(const n of a)if(n&&(n(e,r),r.ignore))return b(t) |
||||
;return n&&n.endSameAsBegin&&(n.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")), |
||||
n.skip?O+=t:(n.excludeBegin&&(O+=t), |
||||
d(),n.returnBegin||n.excludeBegin||(O=t)),h(n),n.returnBegin?0:t.length}(n) |
||||
;if("illegal"===n.type&&!r){ |
||||
const e=Error('Illegal lexeme "'+a+'" for mode "'+(_.className||"<unnamed>")+'"') |
||||
;throw e.mode=_,e}if("end"===n.type){const e=x(n);if(e!==K)return e} |
||||
if("illegal"===n.type&&""===a)return 1 |
||||
;if(B>1e5&&B>3*n.index)throw Error("potential infinite loop, way more iterations than matches") |
||||
;return O+=a,a.length}const w=N(e) |
||||
;if(!w)throw C(l.replace("{}",e)),Error('Unknown language: "'+e+'"') |
||||
;const R=I(w,{plugins:a});let y="",_=o||R;const k={},M=new g.__emitter(g);(()=>{ |
||||
const e=[];for(let t=_;t!==w;t=t.parent)t.className&&e.unshift(t.className) |
||||
;e.forEach((e=>M.openNode(e)))})();let O="",A=0,L=0,B=0,T=!1;try{ |
||||
for(_.matcher.considerAll();;){ |
||||
B++,T?T=!1:_.matcher.considerAll(),_.matcher.lastIndex=L |
||||
;const e=_.matcher.exec(c);if(!e)break;const t=v(c.substring(L,e.index),e) |
||||
;L=e.index+t}return v(c.substr(L)),M.closeAllNodes(),M.finalize(),y=M.toHTML(),{ |
||||
relevance:A,value:y,language:e,illegal:!1,emitter:M,top:_}}catch(t){ |
||||
if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{ |
||||
msg:t.message,context:c.slice(L-100,L+100),mode:t.mode},sofar:y,relevance:0, |
||||
value:U(c),emitter:M};if(i)return{illegal:!1,relevance:0,value:U(c),emitter:M, |
||||
language:e,top:_,errorRaised:t};throw t}}function p(e,t){ |
||||
t=t||g.languages||Object.keys(n);const s=(e=>{const t={relevance:0, |
||||
emitter:new g.__emitter(g),value:U(e),illegal:!1,top:u} |
||||
;return t.emitter.addText(e),t})(e),r=t.filter(N).filter(R).map((t=>f(t,e,!1))) |
||||
;r.unshift(s);const a=r.sort(((e,t)=>{ |
||||
if(e.relevance!==t.relevance)return t.relevance-e.relevance |
||||
;if(e.language&&t.language){if(N(e.language).supersetOf===t.language)return 1 |
||||
;if(N(t.language).supersetOf===e.language)return-1}return 0})),[i,o]=a,l=i |
||||
;return l.second_best=o,l}const m={"before:highlightBlock":({block:e})=>{ |
||||
g.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n")) |
||||
},"after:highlightBlock":({result:e})=>{ |
||||
g.useBR&&(e.value=e.value.replace(/\n/g,"<br>"))}},b=/^(<[^>]+>|\t)+/gm,x={ |
||||
"after:highlightBlock":({result:e})=>{ |
||||
g.tabReplace&&(e.value=e.value.replace(b,(e=>e.replace(/\t/g,g.tabReplace))))}} |
||||
;function E(e){let t=null;const n=(e=>{let t=e.className+" " |
||||
;t+=e.parentNode?e.parentNode.className:"";const n=g.languageDetectRe.exec(t) |
||||
;if(n){const t=N(n[1]) |
||||
;return t||(H(l.replace("{}",n[1])),H("Falling back to no-highlight mode for this block.",e)), |
||||
t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>d(e)||N(e)))})(e) |
||||
;if(d(n))return;_("before:highlightBlock",{block:e,language:n}),t=e |
||||
;const s=t.textContent,a=n?h(n,s,!0):p(s);_("after:highlightBlock",{block:e, |
||||
result:a,text:s}),e.innerHTML=a.value,((e,t,n)=>{const s=t?r[t]:n |
||||
;e.classList.add("hljs"),s&&e.classList.add(s)})(e,n,a.language),e.result={ |
||||
language:a.language,re:a.relevance,relavance:a.relevance |
||||
},a.second_best&&(e.second_best={language:a.second_best.language, |
||||
re:a.second_best.relevance,relavance:a.second_best.relevance})}const v=()=>{ |
||||
v.called||(v.called=!0,document.querySelectorAll("pre code").forEach(E))} |
||||
;function N(e){return e=(e||"").toLowerCase(),n[e]||n[r[e]]} |
||||
function w(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e]=t |
||||
}))}function R(e){const t=N(e);return t&&!t.disableAutodetect}function _(e,t){ |
||||
const n=e;a.forEach((e=>{e[n]&&e[n](t)}))}Object.assign(e,{highlight:h, |
||||
highlightAuto:p,fixMarkup:e=>{ |
||||
return $("10.2.0","fixMarkup will be removed entirely in v11.0"), |
||||
$("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"), |
||||
t=e, |
||||
g.tabReplace||g.useBR?t.replace(o,(e=>"\n"===e?g.useBR?"<br>":e:g.tabReplace?e.replace(/\t/g,g.tabReplace):e)):t |
||||
;var t},highlightBlock:E,configure:e=>{ |
||||
e.useBR&&($("10.3.0","'useBR' will be removed entirely in v11.0"), |
||||
$("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")), |
||||
g=z(g,e)},initHighlighting:v,initHighlightingOnLoad:()=>{ |
||||
window.addEventListener("DOMContentLoaded",v,!1)},registerLanguage:(t,s)=>{ |
||||
let r=null;try{r=s(e)}catch(e){ |
||||
if(C("Language definition for '{}' could not be registered.".replace("{}",t)), |
||||
!i)throw e;C(e),r=u} |
||||
r.name||(r.name=t),n[t]=r,r.rawDefinition=s.bind(null,e),r.aliases&&w(r.aliases,{ |
||||
languageName:t})},listLanguages:()=>Object.keys(n),getLanguage:N, |
||||
registerAliases:w,requireLanguage:e=>{ |
||||
$("10.4.0","requireLanguage will be removed entirely in v11."), |
||||
$("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844") |
||||
;const t=N(e);if(t)return t |
||||
;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))}, |
||||
autoDetection:R,inherit:z,addPlugin:e=>{a.push(e)},vuePlugin:j(e).VuePlugin |
||||
}),e.debugMode=()=>{i=!1},e.safeMode=()=>{i=!0},e.versionString="10.5.0" |
||||
;for(const e in y)"object"==typeof y[e]&&t(y[e]) |
||||
;return Object.assign(e,y),e.addPlugin(m),e.addPlugin(S),e.addPlugin(x),e})({}) |
||||
}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("xml",(()=>{"use strict";function e(e){ |
||||
return e?"string"==typeof e?e:e.source:null}function n(e){return a("(?=",e,")")} |
||||
function a(...n){return n.map((n=>e(n))).join("")}function s(...n){ |
||||
return"("+n.map((n=>e(n))).join("|")+")"}return e=>{ |
||||
const t=a(/[A-Z_]/,a("(",/[A-Z0-9_.-]+:/,")?"),/[A-Z0-9_.-]*/),i={ |
||||
className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},r={begin:/\s/, |
||||
contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] |
||||
},c=e.inherit(r,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{ |
||||
className:"meta-string"}),g=e.inherit(e.QUOTE_STRING_MODE,{ |
||||
className:"meta-string"}),m={endsWithParent:!0,illegal:/</,relevance:0, |
||||
contains:[{className:"attr",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\s*/, |
||||
relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/, |
||||
end:/"/,contains:[i]},{begin:/'/,end:/'/,contains:[i]},{begin:/[^\s"'=<>`]+/}]}] |
||||
}]};return{name:"HTML, XML", |
||||
aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], |
||||
case_insensitive:!0,contains:[{className:"meta",begin:/<![a-z]/,end:/>/, |
||||
relevance:10,contains:[r,g,l,c,{begin:/\[/,end:/\]/,contains:[{className:"meta", |
||||
begin:/<![a-z]/,end:/>/,contains:[r,c,g,l]}]}]},e.COMMENT(/<!--/,/-->/,{ |
||||
relevance:10}),{begin:/<!\[CDATA\[/,end:/\]\]>/,relevance:10},i,{ |
||||
className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag", |
||||
begin:/<style(?=\s|>)/,end:/>/,keywords:{name:"style"},contains:[m],starts:{ |
||||
end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", |
||||
begin:/<script(?=\s|>)/,end:/>/,keywords:{name:"script"},contains:[m],starts:{ |
||||
end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ |
||||
className:"tag",begin:/<>|<\/>/},{className:"tag", |
||||
begin:a(/</,n(a(t,s(/\/>/,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name", |
||||
begin:t,relevance:0,starts:m}]},{className:"tag",begin:a(/<\//,n(a(t,/>/))), |
||||
contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0}]}]}} |
||||
})());hljs.registerLanguage("css",(()=>{"use strict";return e=>{ |
||||
var n="[a-zA-Z-][a-zA-Z0-9_-]*",a={ |
||||
begin:/([*]\s?)?(?:[A-Z_.\-\\]+|--[a-zA-Z0-9_-]+)\s*(\/\*\*\/)?:/, |
||||
returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute", |
||||
begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0, |
||||
contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in", |
||||
begin:/[\w-]+/},{begin:/\(/,end:/\)/, |
||||
contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}] |
||||
},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{ |
||||
className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}] |
||||
}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/, |
||||
contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id", |
||||
begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:"\\."+n},{ |
||||
className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", |
||||
contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo", |
||||
begin:/:(:)?[a-zA-Z0-9_+()"'.-]+/},{begin:"@(page|font-face)", |
||||
lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]", |
||||
illegal:/:/,returnBegin:!0,contains:[{className:"keyword", |
||||
begin:/@-?\w[\w]*(-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0, |
||||
relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/, |
||||
className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE] |
||||
}]},{className:"selector-tag",begin:n,relevance:0},{begin:/\{/,end:/\}/, |
||||
illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,{begin:/;/},a]}]}}})());hljs.registerLanguage("java",(()=>{"use strict" |
||||
;var e="\\.([0-9](_*[0-9])*)",n="[0-9a-fA-F](_*[0-9a-fA-F])*",a={ |
||||
className:"number",variants:[{ |
||||
begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b` |
||||
},{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{ |
||||
begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{ |
||||
begin:`\\b0[xX]((${n})\\.?|(${n})?\\.(${n}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b` |
||||
},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${n})[lL]?\\b`},{ |
||||
begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}], |
||||
relevance:0};return e=>{ |
||||
var n="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s={ |
||||
className:"meta",begin:"@[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*", |
||||
contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};const r=a;return{ |
||||
name:"Java",aliases:["jsp"],keywords:n,illegal:/<\/|#/, |
||||
contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/, |
||||
relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{ |
||||
begin:/import java\.[a-z]+\./,keywords:"import",relevance:2 |
||||
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{ |
||||
className:"class",beginKeywords:"class interface enum",end:/[{;=]/, |
||||
excludeEnd:!0,keywords:"class interface enum",illegal:/[:"\[\]]/,contains:[{ |
||||
beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ |
||||
beginKeywords:"new throw return else",relevance:0},{className:"class", |
||||
begin:"record\\s+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,excludeEnd:!0, |
||||
end:/[{;=]/,keywords:n,contains:[{beginKeywords:"record"},{ |
||||
begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, |
||||
contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/, |
||||
keywords:n,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE] |
||||
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"function", |
||||
begin:"([\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(<[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(", |
||||
returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:n,contains:[{ |
||||
begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, |
||||
contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/, |
||||
keywords:n,relevance:0, |
||||
contains:[s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,r,e.C_BLOCK_COMMENT_MODE] |
||||
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},r,s]}}})());hljs.registerLanguage("markdown",(()=>{"use strict";function n(...n){ |
||||
return n.map((n=>{return(e=n)?"string"==typeof e?e:e.source:null;var e |
||||
})).join("")}return e=>{const a={begin:/<\/?[A-Za-z_]/,end:">", |
||||
subLanguage:"xml",relevance:0},i={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0 |
||||
},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, |
||||
relevance:2},{begin:n(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), |
||||
relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ |
||||
begin:/\[.+?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{ |
||||
className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, |
||||
returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", |
||||
excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", |
||||
end:"\\]",excludeBegin:!0,excludeEnd:!0}]},s={className:"strong",contains:[], |
||||
variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},c={ |
||||
className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{ |
||||
begin:/_(?!_)/,end:/_/,relevance:0}]};s.contains.push(c),c.contains.push(s) |
||||
;let t=[a,i] |
||||
;return s.contains=s.contains.concat(t),c.contains=c.contains.concat(t), |
||||
t=t.concat(s,c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ |
||||
className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:t},{ |
||||
begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", |
||||
contains:t}]}]},a,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", |
||||
end:"\\s+",excludeEnd:!0},s,c,{className:"quote",begin:"^>\\s+",contains:t, |
||||
end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ |
||||
begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ |
||||
begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", |
||||
contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ |
||||
begin:"^[-\\*]{3,}",end:"$"},i,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ |
||||
className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ |
||||
className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})());hljs.registerLanguage("json",(()=>{"use strict";return n=>{const e={ |
||||
literal:"true false null" |
||||
},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],a=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],l={ |
||||
end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:e},t={begin:/\{/, |
||||
end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/, |
||||
contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(l,{begin:/:/ |
||||
})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(l)], |
||||
illegal:"\\S"};return a.push(t,s),i.forEach((n=>{a.push(n)})),{name:"JSON", |
||||
contains:a,keywords:e,illegal:"\\S"}}})());hljs.registerLanguage("javascript",(()=>{"use strict" |
||||
;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) |
||||
;function r(e){return i("(?=",e,")")}function i(...e){return e.map((e=>{ |
||||
return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}return t=>{ |
||||
const c=e,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/, |
||||
isTrulyOpeningTag:(e,n)=>{const a=e[0].length+e.index,s=e.input[a] |
||||
;"<"!==s?">"===s&&(((e,{after:n})=>{const a="</"+e[0].slice(1) |
||||
;return-1!==e.input.indexOf(a,n)})(e,{after:a |
||||
})||n.ignoreMatch()):n.ignoreMatch()}},l={$pattern:e,keyword:n.join(" "), |
||||
literal:a.join(" "),built_in:s.join(" ") |
||||
},b="\\.([0-9](_?[0-9])*)",g="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",d={ |
||||
className:"number",variants:[{ |
||||
begin:`(\\b(${g})((${b})|\\.)?|(${b}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{ |
||||
begin:`\\b(${g})\\b((${b})\\b|\\.)?|(${b})\\b`},{ |
||||
begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{ |
||||
begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{ |
||||
begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{ |
||||
begin:"\\b0[0-7]+n?\\b"}],relevance:0},E={className:"subst",begin:"\\$\\{", |
||||
end:"\\}",keywords:l,contains:[]},u={begin:"html`",end:"",starts:{end:"`", |
||||
returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},_={ |
||||
begin:"css`",end:"",starts:{end:"`",returnEnd:!1, |
||||
contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},m={className:"string", |
||||
begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]},N={className:"comment", |
||||
variants:[t.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{ |
||||
className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{", |
||||
end:"\\}",relevance:0},{className:"variable",begin:c+"(?=\\s*(-)|$)", |
||||
endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}] |
||||
}),t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE] |
||||
},y=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,u,_,m,d,t.REGEXP_MODE] |
||||
;E.contains=y.concat({begin:/\{/,end:/\}/,keywords:l,contains:["self"].concat(y) |
||||
});const f=[].concat(N,E.contains),A=f.concat([{begin:/\(/,end:/\)/,keywords:l, |
||||
contains:["self"].concat(f)}]),p={className:"params",begin:/\(/,end:/\)/, |
||||
excludeBegin:!0,excludeEnd:!0,keywords:l,contains:A};return{name:"Javascript", |
||||
aliases:["js","jsx","mjs","cjs"],keywords:l,exports:{PARAMS_CONTAINS:A}, |
||||
illegal:/#(?![$_A-z])/,contains:[t.SHEBANG({label:"shebang",binary:"node", |
||||
relevance:5}),{label:"use_strict",className:"meta",relevance:10, |
||||
begin:/^\s*['"]use (strict|asm)['"]/ |
||||
},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,u,_,m,N,d,{ |
||||
begin:i(/[{,\n]\s*/,r(i(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,c+"\\s*:"))), |
||||
relevance:0,contains:[{className:"attr",begin:c+r("\\s*:"),relevance:0}]},{ |
||||
begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", |
||||
keywords:"return throw case",contains:[N,t.REGEXP_MODE,{className:"function", |
||||
begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>", |
||||
returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{ |
||||
begin:t.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0 |
||||
},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:l,contains:A}]}] |
||||
},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{ |
||||
variants:[{begin:"<>",end:"</>"},{begin:o.begin,"on:begin":o.isTrulyOpeningTag, |
||||
end:o.end}],subLanguage:"xml",contains:[{begin:o.begin,end:o.end,skip:!0, |
||||
contains:["self"]}]}],relevance:0},{className:"function", |
||||
beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:l, |
||||
contains:["self",t.inherit(t.TITLE_MODE,{begin:c}),p],illegal:/%/},{ |
||||
beginKeywords:"while if switch catch for"},{className:"function", |
||||
begin:t.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", |
||||
returnBegin:!0,contains:[p,t.inherit(t.TITLE_MODE,{begin:c})]},{variants:[{ |
||||
begin:"\\."+c},{begin:"\\$"+c}],relevance:0},{className:"class", |
||||
beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{ |
||||
beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/, |
||||
end:/[{;]/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:c}),"self",p] |
||||
},{begin:"(get|set)\\s+(?="+c+"\\()",end:/\{/,keywords:"get set", |
||||
contains:[t.inherit(t.TITLE_MODE,{begin:c}),{begin:/\(\)/},p]},{begin:/\$[(.]/}] |
||||
}}})()); |
@ -0,0 +1,85 @@ |
||||
/* |
||||
|
||||
Style with support for rainbow parens |
||||
|
||||
*/ |
||||
|
||||
.hljs { |
||||
display: block; |
||||
overflow-x: auto; |
||||
padding: 0.5em; |
||||
background: #474949; |
||||
color: #d1d9e1; |
||||
} |
||||
|
||||
|
||||
.hljs-comment, |
||||
.hljs-quote { |
||||
color: #969896; |
||||
font-style: italic; |
||||
} |
||||
|
||||
.hljs-keyword, |
||||
.hljs-selector-tag, |
||||
.hljs-literal, |
||||
.hljs-type, |
||||
.hljs-addition { |
||||
color: #cc99cc; |
||||
} |
||||
|
||||
.hljs-number, |
||||
.hljs-selector-attr, |
||||
.hljs-selector-pseudo { |
||||
color: #f99157; |
||||
} |
||||
|
||||
.hljs-string, |
||||
.hljs-doctag, |
||||
.hljs-regexp { |
||||
color: #8abeb7; |
||||
} |
||||
|
||||
.hljs-title, |
||||
.hljs-name, |
||||
.hljs-section, |
||||
.hljs-built_in { |
||||
color: #b5bd68; |
||||
} |
||||
|
||||
.hljs-variable, |
||||
.hljs-template-variable, |
||||
.hljs-selector-id, |
||||
.hljs-class .hljs-title { |
||||
color: #ffcc66; |
||||
} |
||||
|
||||
.hljs-section, |
||||
.hljs-name, |
||||
.hljs-strong { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.hljs-symbol, |
||||
.hljs-bullet, |
||||
.hljs-subst, |
||||
.hljs-meta, |
||||
.hljs-link { |
||||
color: #f99157; |
||||
} |
||||
|
||||
.hljs-deletion { |
||||
color: #dc322f; |
||||
} |
||||
|
||||
.hljs-formula { |
||||
background: #eee8d5; |
||||
} |
||||
|
||||
.hljs-attr, |
||||
.hljs-attribute { |
||||
color: #81a2be; |
||||
} |
||||
|
||||
.hljs-emphasis { |
||||
font-style: italic; |
||||
} |
@ -1,3 +0,0 @@ |
||||
1、 正确填写WebDAV 服务器地址、WebDAV 账号、WebDAV 密码;(要获得这三项的信息,需要注册一个坚果云账号,如果直接在手机上注册,坚果云会让你下载app,过程比较麻烦,为了一步到位,最好是在电脑上打开这个注册链接:https://www.jianguoyun.com/d/signup;注册后,进入坚果云;点击右上角账户名处选择 “账户信息”,然后选择“安全选项”;在“安全选项” 中找到“第三方应用管理”,并选择“添加应用”,输入名称如“阅读”后,会生成密码,选择完成;其中https://dav.jianguoyun.com/dav/就是填入“WebDAV 服务器地址”的内容,“使用情况”后面的邮箱地址就是你的“WebDAV 账号”,点击显示密码后得到的密码就是你的“WebDAV 密码”。) |
||||
|
||||
2、 无需操作,APP默认每天自动云备份一次。 |
@ -0,0 +1,30 @@ |
||||
package xyz.fycz.myreader.entity.sourcedebug |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/12 21:11 |
||||
*/ |
||||
data class DebugBook( |
||||
/*var name: String? = null, |
||||
var author: String? = null, |
||||
var type: String? = null, |
||||
var desc: String? = null, |
||||
var wordCount: String? = null, |
||||
var status: String? = null, |
||||
var lastChapter: String? = null, |
||||
var updateTime: String? = null, |
||||
var imgUrl: String? = null, |
||||
var tocUrl: String? = null, |
||||
var infoUrl: String? = null,*/ |
||||
var 书名: String? = null, |
||||
var 作者: String? = null, |
||||
var 分类: String? = null, |
||||
var 简介: String? = null, |
||||
var 字数: String? = null, |
||||
var 连载状态: String? = null, |
||||
var 最新章节: String? = null, |
||||
var 更新时间: String? = null, |
||||
var 封面链接: String? = null, |
||||
var 目录链接: String? = null, |
||||
var 详情链接: String? = null, |
||||
) |
@ -0,0 +1,13 @@ |
||||
package xyz.fycz.myreader.entity.sourcedebug |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/12 21:21 |
||||
*/ |
||||
data class DebugChapter( |
||||
/*var title: String? = null, |
||||
var url: String? = null, |
||||
var content: String? = null*/ |
||||
var 章节名称: String? = null, |
||||
var 章节链接: String? = null, |
||||
) |
@ -0,0 +1,100 @@ |
||||
package xyz.fycz.myreader.entity.sourcedebug; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/12 19:35 |
||||
*/ |
||||
public class DebugEntity implements Parcelable { |
||||
public static final int SEARCH = 0; |
||||
public static final int INFO = 1; |
||||
public static final int TOC = 2; |
||||
public static final int CONTENT = 3; |
||||
|
||||
private int debugMode; |
||||
private BookSource bookSource; |
||||
private String url; |
||||
private String parseResult; |
||||
private String html; |
||||
|
||||
public DebugEntity() { |
||||
} |
||||
|
||||
protected DebugEntity(Parcel in) { |
||||
debugMode = in.readInt(); |
||||
bookSource = in.readParcelable(BookSource.class.getClassLoader()); |
||||
url = in.readString(); |
||||
parseResult = in.readString(); |
||||
html = in.readString(); |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeInt(debugMode); |
||||
dest.writeParcelable(bookSource, flags); |
||||
dest.writeString(url); |
||||
dest.writeString(parseResult); |
||||
dest.writeString(html); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<DebugEntity> CREATOR = new Creator<DebugEntity>() { |
||||
@Override |
||||
public DebugEntity createFromParcel(Parcel in) { |
||||
return new DebugEntity(in); |
||||
} |
||||
|
||||
@Override |
||||
public DebugEntity[] newArray(int size) { |
||||
return new DebugEntity[size]; |
||||
} |
||||
}; |
||||
|
||||
public int getDebugMode() { |
||||
return debugMode; |
||||
} |
||||
|
||||
public void setDebugMode(int debugMode) { |
||||
this.debugMode = debugMode; |
||||
} |
||||
|
||||
public BookSource getBookSource() { |
||||
return bookSource; |
||||
} |
||||
|
||||
public void setBookSource(BookSource bookSource) { |
||||
this.bookSource = bookSource; |
||||
} |
||||
|
||||
public String getUrl() { |
||||
return url; |
||||
} |
||||
|
||||
public void setUrl(String url) { |
||||
this.url = url; |
||||
} |
||||
|
||||
public String getParseResult() { |
||||
return parseResult; |
||||
} |
||||
|
||||
public void setParseResult(String parseResult) { |
||||
this.parseResult = parseResult; |
||||
} |
||||
|
||||
public String getHtml() { |
||||
return html; |
||||
} |
||||
|
||||
public void setHtml(String html) { |
||||
this.html = html; |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
package xyz.fycz.myreader.entity.sourcedebug |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/15 22:37 |
||||
*/ |
||||
data class ListResult( |
||||
var 信息: String? = null, |
||||
var 结果: Any? = null, |
||||
) |
@ -0,0 +1,12 @@ |
||||
package xyz.fycz.myreader.entity.sourceedit |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/9 10:15 |
||||
*/ |
||||
data class EditEntity( |
||||
var key: String, |
||||
var value: String? = "", |
||||
var hint: Int, |
||||
var tip: String? = "规则后可接##加@r/@a/@c/@nc函数" |
||||
) |
@ -0,0 +1,181 @@ |
||||
package xyz.fycz.myreader.entity.sourceedit |
||||
|
||||
import xyz.fycz.myreader.R |
||||
import xyz.fycz.myreader.greendao.entity.rule.* |
||||
import java.util.* |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/9 12:03 |
||||
*/ |
||||
object EditEntityUtil { |
||||
fun getSourceEntities(source: BookSource? = BookSource()): List<EditEntity> { |
||||
val sourceEntities: ArrayList<EditEntity> = ArrayList() |
||||
sourceEntities.apply { |
||||
add(EditEntity("sourceUrl", source?.sourceUrl, R.string.source_url, "不能为空且唯一")) |
||||
add(EditEntity("sourceName", source?.sourceName, R.string.source_name, "不能为空")) |
||||
add(EditEntity("sourceGroup", source?.sourceGroup, R.string.source_group, "不同分组以;/,隔开")) |
||||
add(EditEntity("sourceCharset", source?.sourceCharset, R.string.source_charset, "默认UTF-8")) |
||||
add(EditEntity("sourceComment", source?.sourceComment, R.string.comment, "这是您留给使用者的说明")) |
||||
} |
||||
return sourceEntities |
||||
} |
||||
|
||||
fun getSearchEntities(searchRule: SearchRule? = SearchRule()): List<EditEntity> { |
||||
val searchEntities: ArrayList<EditEntity> = ArrayList() |
||||
searchEntities.apply { |
||||
add(EditEntity("searchUrl", searchRule?.searchUrl, R.string.r_search_url, |
||||
"搜索关键词以{key}进行占位;post请求以“,”分隔url,“,”前是搜索地址,“,”后是请求体")) |
||||
add(EditEntity("charset", searchRule?.charset, R.string.r_search_charset, "默认使用书源字符编码")) |
||||
add(EditEntity("list", searchRule?.list, R.string.r_book_list, |
||||
"对于Matcher解析器:此处填写书籍列表所在区间,支持普通函数;" + |
||||
"\n对于Xpath/JsonPath解析器:此处填写书籍列表规则,不支持普通函数,规则后接##!加数字可以跳过列表前几个")) |
||||
add(EditEntity("name", searchRule?.name, R.string.r_book_name)) |
||||
add(EditEntity("author", searchRule?.author, R.string.r_author)) |
||||
add(EditEntity("type", searchRule?.type, R.string.rule_book_type)) |
||||
add(EditEntity("wordCount", searchRule?.wordCount, R.string.rule_word_count)) |
||||
add(EditEntity("status", searchRule?.status, R.string.rule_status)) |
||||
add(EditEntity("desc", searchRule?.desc, R.string.rule_book_desc)) |
||||
add(EditEntity("lastChapter", searchRule?.lastChapter, R.string.rule_last_chapter)) |
||||
add(EditEntity("updateTime", searchRule?.updateTime, R.string.rule_update_time)) |
||||
add(EditEntity("imgUrl", searchRule?.imgUrl, R.string.rule_img_url)) |
||||
add(EditEntity("tocUrl", searchRule?.tocUrl, R.string.rule_toc_url)) |
||||
add(EditEntity("infoUrl", searchRule?.infoUrl, R.string.r_info_url, "为空时使用目录URL")) |
||||
add(EditEntity("relatedWithInfo", searchRule?.isRelatedWithInfo?.toString(), R.string.r_search_related_with_info, |
||||
"搜索时是否关联书籍详情页,填true/t表示关联,false/f表示不关联,测试时不生效")) |
||||
} |
||||
return searchEntities |
||||
} |
||||
|
||||
fun getInfoEntities(infoRule: InfoRule? = InfoRule()): List<EditEntity> { |
||||
val infoEntities: ArrayList<EditEntity> = ArrayList() |
||||
infoEntities.apply { |
||||
add(EditEntity("name", infoRule?.name, R.string.r_book_name)) |
||||
add(EditEntity("author", infoRule?.author, R.string.r_author)) |
||||
add(EditEntity("type", infoRule?.type, R.string.rule_book_type)) |
||||
add(EditEntity("desc", infoRule?.desc, R.string.rule_book_desc)) |
||||
add(EditEntity("status", infoRule?.status, R.string.rule_status)) |
||||
add(EditEntity("wordCount", infoRule?.wordCount, R.string.rule_word_count)) |
||||
add(EditEntity("lastChapter", infoRule?.lastChapter, R.string.rule_last_chapter)) |
||||
add(EditEntity("updateTime", infoRule?.updateTime, R.string.rule_update_time)) |
||||
add(EditEntity("imgUrl", infoRule?.imgUrl, R.string.rule_img_url)) |
||||
add(EditEntity("tocUrl", infoRule?.tocUrl, R.string.rule_toc_url)) |
||||
} |
||||
return infoEntities |
||||
} |
||||
|
||||
fun getTocEntities(tocRule: TocRule? = TocRule()): List<EditEntity> { |
||||
val tocEntities: ArrayList<EditEntity> = ArrayList() |
||||
tocEntities.apply { |
||||
add(EditEntity("chapterBaseUrl", tocRule?.chapterBaseUrl, R.string.rule_chapter_base_url, |
||||
"如果章节URL(一般为相对路径)无法定位章节,可填写此规则获取,默认为书源URL")) |
||||
add(EditEntity("chapterList", tocRule?.chapterList, R.string.rule_chapter_list, |
||||
"对于Mathcer解析器:此处填写书籍列表所在区间,支持普通函数;" + |
||||
"\n对于Xpath/JsonPath解析器:此处填写书籍列表规则,不支持普通函数,规则后接##!加数字可以跳过列表前几个")) |
||||
add(EditEntity("chapterName", tocRule?.chapterName, R.string.rule_chapter_name, |
||||
"对于Mathcer解析器:此处填写章节名称和URL规则,其中章节名称以<title>占位,章节URL以<link>占位,不支持普通函数,规则后接##!加数字可以跳过列表前几个\n" + |
||||
"对于Xpath/JsonPath解析器:此处填写章节名称,支持普通函数")) |
||||
add(EditEntity("chapterUrl", tocRule?.chapterUrl, R.string.rule_chapter_url, |
||||
"对于Mathcer解析器:此处不用填写\n" + |
||||
"对于Xpath/JsonPath解析器:此处填写章节URL规则")) |
||||
add(EditEntity("tocUrlNext", tocRule?.tocUrlNext, R.string.rule_next_toc_url, |
||||
"填写后获取目录时将会不断地从目录下一页获取章节,直至下一页URL为空时停止,注意:千万不要获取恒存在的URL,否则将出现死循环甚至崩溃")) |
||||
} |
||||
return tocEntities |
||||
} |
||||
|
||||
fun getContentEntities(contentRule: ContentRule? = ContentRule()): List<EditEntity> { |
||||
val contentEntities: ArrayList<EditEntity> = ArrayList() |
||||
contentEntities.apply { |
||||
add(EditEntity("content", contentRule?.content, R.string.rule_book_content)) |
||||
add(EditEntity("contentBaseUrl", contentRule?.contentBaseUrl, R.string.rule_base_url_content, |
||||
"如果下一页URL(一般为相对路径)无法定位下一页,可填写此规则获取,默认为书源URL")) |
||||
add(EditEntity("contentUrlNext", contentRule?.contentUrlNext, R.string.rule_next_content, |
||||
"填写后正文时将会不断地从下一页获取内容,直至下一页URL为空时停止,注意:千万不要获取恒存在的URL,否则将出现死循环甚至崩溃")) |
||||
} |
||||
return contentEntities |
||||
} |
||||
|
||||
fun getSource(bookSource: BookSource, sourceEntities: List<EditEntity>): BookSource { |
||||
val source = bookSource.clone() as BookSource |
||||
sourceEntities.forEach { |
||||
when (it.key) { |
||||
"sourceUrl" -> source.sourceUrl = it.value |
||||
"sourceName" -> source.sourceName = it.value |
||||
"sourceGroup" -> source.sourceGroup = it.value |
||||
"sourceCharset" -> source.sourceCharset = it.value |
||||
"sourceComment" -> source.sourceComment = it.value |
||||
} |
||||
} |
||||
return source |
||||
} |
||||
|
||||
fun getSearchRule(searchEntities: List<EditEntity>): SearchRule { |
||||
val searchRule = SearchRule() |
||||
searchEntities.forEach { |
||||
when (it.key) { |
||||
"searchUrl" -> searchRule.searchUrl = it.value |
||||
"charset" -> searchRule.charset = it.value |
||||
"list" -> searchRule.list = it.value |
||||
"name" -> searchRule.name = it.value |
||||
"author" -> searchRule.author = it.value |
||||
"type" -> searchRule.type = it.value |
||||
"desc" -> searchRule.desc = it.value |
||||
"wordCount" -> searchRule.wordCount = it.value |
||||
"status" -> searchRule.status = it.value |
||||
"lastChapter" -> searchRule.lastChapter = it.value |
||||
"updateTime" -> searchRule.updateTime = it.value |
||||
"imgUrl" -> searchRule.imgUrl = it.value |
||||
"tocUrl" -> searchRule.tocUrl = it.value |
||||
"infoUrl" -> searchRule.infoUrl = it.value |
||||
"relatedWithInfo" -> searchRule.isRelatedWithInfo = it.value?.contains("t") == true |
||||
} |
||||
} |
||||
return searchRule |
||||
} |
||||
|
||||
fun getInfoRule(infoRuleEntities: List<EditEntity>): InfoRule { |
||||
val infoRule = InfoRule() |
||||
infoRuleEntities.forEach { |
||||
when (it.key) { |
||||
"name" -> infoRule.name = it.value |
||||
"author" -> infoRule.author = it.value |
||||
"type" -> infoRule.type = it.value |
||||
"desc" -> infoRule.desc = it.value |
||||
"status" -> infoRule.status = it.value |
||||
"wordCount" -> infoRule.wordCount = it.value |
||||
"lastChapter" -> infoRule.lastChapter = it.value |
||||
"updateTime" -> infoRule.updateTime = it.value |
||||
"imgUrl" -> infoRule.imgUrl = it.value |
||||
"tocUrl" -> infoRule.tocUrl = it.value |
||||
} |
||||
} |
||||
return infoRule |
||||
} |
||||
|
||||
fun getTocRule(infoRuleEntities: List<EditEntity>): TocRule { |
||||
val tocRule = TocRule() |
||||
infoRuleEntities.forEach { |
||||
when (it.key) { |
||||
"chapterList" -> tocRule.chapterList = it.value |
||||
"chapterBaseUrl" -> tocRule.chapterBaseUrl = it.value |
||||
"chapterName" -> tocRule.chapterName = it.value |
||||
"chapterUrl" -> tocRule.chapterUrl = it.value |
||||
"tocUrlNext" -> tocRule.tocUrlNext = it.value |
||||
} |
||||
} |
||||
return tocRule |
||||
} |
||||
|
||||
fun getContentRule(infoRuleEntities: List<EditEntity>): ContentRule { |
||||
val contentRule = ContentRule() |
||||
infoRuleEntities.forEach { |
||||
when (it.key) { |
||||
"content" -> contentRule.content = it.value |
||||
"contentBaseUrl" -> contentRule.contentBaseUrl = it.value |
||||
"contentUrlNext" -> contentRule.contentUrlNext = it.value |
||||
} |
||||
} |
||||
return contentRule |
||||
} |
||||
} |
@ -1,80 +0,0 @@ |
||||
package xyz.fycz.myreader.enums; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.MyApplication; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
|
||||
/** |
||||
* 小说源 |
||||
*/ |
||||
|
||||
public enum BookSource { |
||||
local("本地书籍"), |
||||
fynovel("风月小说"), |
||||
tianlai(MyApplication.getApplication().getString(R.string.read_tianlai)), |
||||
biquge44(MyApplication.getApplication().getString(R.string.read_biquge44)), |
||||
//pinshu(MyApplication.getApplication().getString(R.string.read_pinshu)),
|
||||
biquge(MyApplication.getApplication().getString(R.string.read_biquge)), |
||||
qb5(MyApplication.getApplication().getString(R.string.read_qb5)), |
||||
miqu(MyApplication.getApplication().getString(R.string.read_miqu)), |
||||
jiutao(MyApplication.getApplication().getString(R.string.read_jiutao)), |
||||
miaobi(MyApplication.getApplication().getString(R.string.read_miaobi)), |
||||
dstq(MyApplication.getApplication().getString(R.string.read_dstq)), |
||||
yunzhong(MyApplication.getApplication().getString(R.string.read_yunzhong)), |
||||
sonovel(MyApplication.getApplication().getString(R.string.read_sonovel)), |
||||
quannovel(MyApplication.getApplication().getString(R.string.read_quannovel)), |
||||
//qiqi(MyApplication.getApplication().getString(R.string.read_qiqi)),
|
||||
xs7(MyApplication.getApplication().getString(R.string.read_xs7)), |
||||
du1du(MyApplication.getApplication().getString(R.string.read_du1du)), |
||||
paiotian(MyApplication.getApplication().getString(R.string.read_paiotian)), |
||||
laoyao(MyApplication.getApplication().getString(R.string.read_laoyao)), |
||||
xingxing(MyApplication.getApplication().getString(R.string.read_xingxing)), |
||||
shiguang(MyApplication.getApplication().getString(R.string.read_shiguang)), |
||||
//rexue(MyApplication.getApplication().getString(R.string.read_rexue)),
|
||||
chuanqi(MyApplication.getApplication().getString(R.string.read_chuanqi)), |
||||
xiagu(MyApplication.getApplication().getString(R.string.read_xiagu)), |
||||
hongchen(MyApplication.getApplication().getString(R.string.read_hongchen)), |
||||
bijian(MyApplication.getApplication().getString(R.string.read_bijian)), |
||||
yanqinglou(MyApplication.getApplication().getString(R.string.read_yanqinglou)), |
||||
wolong(MyApplication.getApplication().getString(R.string.read_wolong)), |
||||
ewenxue(MyApplication.getApplication().getString(R.string.read_ewenxue)), |
||||
shuhaige(MyApplication.getApplication().getString(R.string.read_shuhaige)), |
||||
luoqiu(MyApplication.getApplication().getString(R.string.read_luoqiu)), |
||||
zw37(MyApplication.getApplication().getString(R.string.read_zw37)), |
||||
xbiquge(MyApplication.getApplication().getString(R.string.read_xbiquge)), |
||||
zaishuyuan(MyApplication.getApplication().getString(R.string.read_zaishuyuan)), |
||||
chaoxing(MyApplication.getApplication().getString(R.string.read_chaoxing)), |
||||
zuopin(MyApplication.getApplication().getString(R.string.read_zuopin)), |
||||
cangshu99(MyApplication.getApplication().getString(R.string.read_cangshu99)), |
||||
ben100(MyApplication.getApplication().getString(R.string.read_ben100)); |
||||
//liulangcat("流浪猫·实体"),
|
||||
|
||||
public String text; |
||||
|
||||
BookSource(String text) { |
||||
this.text = text; |
||||
} |
||||
|
||||
public static String getFromName(String name){ |
||||
for (BookSource bookSource : BookSource.values()){ |
||||
if (bookSource.text.equals(name)){ |
||||
return bookSource.toString(); |
||||
} |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
public static BookSource get(int var0) { |
||||
return values()[var0]; |
||||
} |
||||
|
||||
public static BookSource fromString(String string) { |
||||
try { |
||||
return valueOf(string); |
||||
} catch (Exception e) { |
||||
ToastUtils.showError(e.getLocalizedMessage()); |
||||
return fynovel; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,80 @@ |
||||
package xyz.fycz.myreader.enums; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.App; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
|
||||
/** |
||||
* 小说源 |
||||
*/ |
||||
|
||||
public enum LocalBookSource { |
||||
local("本地书籍"), |
||||
fynovel("风月小说"), |
||||
tianlai(App.getApplication().getString(R.string.read_tianlai)), |
||||
biquge44(App.getApplication().getString(R.string.read_biquge44)), |
||||
//pinshu(App.getApplication().getString(R.string.read_pinshu)),
|
||||
biquge(App.getApplication().getString(R.string.read_biquge)), |
||||
qb5(App.getApplication().getString(R.string.read_qb5)), |
||||
miqu(App.getApplication().getString(R.string.read_miqu)), |
||||
jiutao(App.getApplication().getString(R.string.read_jiutao)), |
||||
miaobi(App.getApplication().getString(R.string.read_miaobi)), |
||||
dstq(App.getApplication().getString(R.string.read_dstq)), |
||||
yunzhong(App.getApplication().getString(R.string.read_yunzhong)), |
||||
sonovel(App.getApplication().getString(R.string.read_sonovel)), |
||||
quannovel(App.getApplication().getString(R.string.read_quannovel)), |
||||
//qiqi(App.getApplication().getString(R.string.read_qiqi)),
|
||||
xs7(App.getApplication().getString(R.string.read_xs7)), |
||||
du1du(App.getApplication().getString(R.string.read_du1du)), |
||||
paiotian(App.getApplication().getString(R.string.read_paiotian)), |
||||
laoyao(App.getApplication().getString(R.string.read_laoyao)), |
||||
xingxing(App.getApplication().getString(R.string.read_xingxing)), |
||||
shiguang(App.getApplication().getString(R.string.read_shiguang)), |
||||
//rexue(App.getApplication().getString(R.string.read_rexue)),
|
||||
chuanqi(App.getApplication().getString(R.string.read_chuanqi)), |
||||
xiagu(App.getApplication().getString(R.string.read_xiagu)), |
||||
hongchen(App.getApplication().getString(R.string.read_hongchen)), |
||||
bijian(App.getApplication().getString(R.string.read_bijian)), |
||||
yanqinglou(App.getApplication().getString(R.string.read_yanqinglou)), |
||||
wolong(App.getApplication().getString(R.string.read_wolong)), |
||||
ewenxue(App.getApplication().getString(R.string.read_ewenxue)), |
||||
shuhaige(App.getApplication().getString(R.string.read_shuhaige)), |
||||
luoqiu(App.getApplication().getString(R.string.read_luoqiu)), |
||||
zw37(App.getApplication().getString(R.string.read_zw37)), |
||||
xbiquge(App.getApplication().getString(R.string.read_xbiquge)), |
||||
zaishuyuan(App.getApplication().getString(R.string.read_zaishuyuan)), |
||||
chaoxing(App.getApplication().getString(R.string.read_chaoxing)), |
||||
zuopin(App.getApplication().getString(R.string.read_zuopin)), |
||||
cangshu99(App.getApplication().getString(R.string.read_cangshu99)), |
||||
ben100(App.getApplication().getString(R.string.read_ben100)); |
||||
//liulangcat("流浪猫·实体"),
|
||||
|
||||
public String text; |
||||
|
||||
LocalBookSource(String text) { |
||||
this.text = text; |
||||
} |
||||
|
||||
public static String getFromName(String name){ |
||||
for (LocalBookSource bookSource : LocalBookSource.values()){ |
||||
if (bookSource.text.equals(name)){ |
||||
return bookSource.toString(); |
||||
} |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
public static LocalBookSource get(int var0) { |
||||
return values()[var0]; |
||||
} |
||||
|
||||
public static LocalBookSource fromString(String string) { |
||||
try { |
||||
return valueOf(string); |
||||
} catch (Exception e) { |
||||
ToastUtils.showError(e.getLocalizedMessage()); |
||||
return fynovel; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,373 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
import android.text.TextUtils; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.google.gson.Gson; |
||||
|
||||
import org.greenrobot.greendao.annotation.Convert; |
||||
import org.greenrobot.greendao.annotation.Entity; |
||||
import org.greenrobot.greendao.annotation.Id; |
||||
import org.greenrobot.greendao.annotation.NotNull; |
||||
import org.greenrobot.greendao.annotation.OrderBy; |
||||
import org.greenrobot.greendao.annotation.Generated; |
||||
import org.greenrobot.greendao.annotation.Transient; |
||||
import org.greenrobot.greendao.annotation.Unique; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Objects; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.convert.ContentRuleConvert; |
||||
import xyz.fycz.myreader.greendao.entity.rule.convert.FindRuleConvert; |
||||
import xyz.fycz.myreader.greendao.entity.rule.convert.InfoRuleConvert; |
||||
import xyz.fycz.myreader.greendao.entity.rule.convert.SearchRuleConvert; |
||||
import xyz.fycz.myreader.greendao.entity.rule.convert.TocRuleConvert; |
||||
|
||||
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 17:37 |
||||
*/ |
||||
@Entity |
||||
public class BookSource implements Parcelable, Cloneable { |
||||
//基本信息
|
||||
@Id |
||||
private String sourceUrl; |
||||
private String sourceEName;//内置书源标识
|
||||
private String sourceName; |
||||
private String sourceGroup; |
||||
private String sourceCharset; |
||||
private String sourceType; |
||||
private String sourceComment; |
||||
private Long lastUpdateTime; |
||||
|
||||
@OrderBy |
||||
private int orderNum; |
||||
@OrderBy |
||||
@NotNull |
||||
private int weight; |
||||
private boolean enable; |
||||
|
||||
@Transient |
||||
private transient ArrayList<String> groupList; |
||||
|
||||
//搜索规则
|
||||
@Convert(converter = SearchRuleConvert.class, columnType = String.class) |
||||
private SearchRule searchRule; |
||||
//详情规则
|
||||
@Convert(converter = InfoRuleConvert.class, columnType = String.class) |
||||
private InfoRule infoRule; |
||||
//目录规则
|
||||
@Convert(converter = TocRuleConvert.class, columnType = String.class) |
||||
private TocRule tocRule; |
||||
//正文页规则
|
||||
@Convert(converter = ContentRuleConvert.class, columnType = String.class) |
||||
private ContentRule contentRule; |
||||
//发现页规则
|
||||
@Convert(converter = FindRuleConvert.class, columnType = String.class) |
||||
private FindRule findRule; |
||||
|
||||
@Generated(hash = 441434718) |
||||
public BookSource(String sourceUrl, String sourceEName, String sourceName, String sourceGroup, |
||||
String sourceCharset, String sourceType, String sourceComment, Long lastUpdateTime, |
||||
int orderNum, int weight, boolean enable, SearchRule searchRule, InfoRule infoRule, |
||||
TocRule tocRule, ContentRule contentRule, FindRule findRule) { |
||||
this.sourceUrl = sourceUrl; |
||||
this.sourceEName = sourceEName; |
||||
this.sourceName = sourceName; |
||||
this.sourceGroup = sourceGroup; |
||||
this.sourceCharset = sourceCharset; |
||||
this.sourceType = sourceType; |
||||
this.sourceComment = sourceComment; |
||||
this.lastUpdateTime = lastUpdateTime; |
||||
this.orderNum = orderNum; |
||||
this.weight = weight; |
||||
this.enable = enable; |
||||
this.searchRule = searchRule; |
||||
this.infoRule = infoRule; |
||||
this.tocRule = tocRule; |
||||
this.contentRule = contentRule; |
||||
this.findRule = findRule; |
||||
} |
||||
|
||||
@Generated(hash = 2045691642) |
||||
public BookSource() { |
||||
} |
||||
|
||||
|
||||
protected BookSource(Parcel in) { |
||||
sourceUrl = in.readString(); |
||||
sourceEName = in.readString(); |
||||
sourceName = in.readString(); |
||||
sourceGroup = in.readString(); |
||||
sourceCharset = in.readString(); |
||||
sourceType = in.readString(); |
||||
sourceComment = in.readString(); |
||||
if (in.readByte() == 0) { |
||||
lastUpdateTime = null; |
||||
} else { |
||||
lastUpdateTime = in.readLong(); |
||||
} |
||||
orderNum = in.readInt(); |
||||
weight = in.readInt(); |
||||
enable = in.readByte() != 0; |
||||
searchRule = in.readParcelable(SearchRule.class.getClassLoader()); |
||||
infoRule = in.readParcelable(InfoRule.class.getClassLoader()); |
||||
tocRule = in.readParcelable(TocRule.class.getClassLoader()); |
||||
contentRule = in.readParcelable(ContentRule.class.getClassLoader()); |
||||
findRule = in.readParcelable(FindRule.class.getClassLoader()); |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeString(sourceUrl); |
||||
dest.writeString(sourceEName); |
||||
dest.writeString(sourceName); |
||||
dest.writeString(sourceGroup); |
||||
dest.writeString(sourceCharset); |
||||
dest.writeString(sourceType); |
||||
dest.writeString(sourceComment); |
||||
if (lastUpdateTime == null) { |
||||
dest.writeByte((byte) 0); |
||||
} else { |
||||
dest.writeByte((byte) 1); |
||||
dest.writeLong(lastUpdateTime); |
||||
} |
||||
dest.writeInt(orderNum); |
||||
dest.writeInt(weight); |
||||
dest.writeByte((byte) (enable ? 1 : 0)); |
||||
dest.writeParcelable(searchRule, flags); |
||||
dest.writeParcelable(infoRule, flags); |
||||
dest.writeParcelable(tocRule, flags); |
||||
dest.writeParcelable(contentRule, flags); |
||||
dest.writeParcelable(findRule, flags); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<BookSource> CREATOR = new Creator<BookSource>() { |
||||
@Override |
||||
public BookSource createFromParcel(Parcel in) { |
||||
return new BookSource(in); |
||||
} |
||||
|
||||
@Override |
||||
public BookSource[] newArray(int size) { |
||||
return new BookSource[size]; |
||||
} |
||||
}; |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
BookSource source = (BookSource) o; |
||||
return enable == source.enable && |
||||
stringEquals(sourceUrl, source.sourceUrl) && |
||||
stringEquals(sourceEName, source.sourceEName) && |
||||
stringEquals(sourceName, source.sourceName) && |
||||
stringEquals(sourceGroup, source.sourceGroup) && |
||||
stringEquals(sourceCharset, source.sourceCharset) && |
||||
stringEquals(sourceType, source.sourceType) && |
||||
stringEquals(sourceComment, source.sourceComment) && |
||||
Objects.equals(searchRule, source.searchRule) && |
||||
Objects.equals(infoRule, source.infoRule) && |
||||
Objects.equals(tocRule, source.tocRule) && |
||||
Objects.equals(contentRule, source.contentRule) && |
||||
Objects.equals(findRule, source.findRule); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public Object clone() { |
||||
try { |
||||
Gson gson = new Gson(); |
||||
String json = gson.toJson(this); |
||||
return gson.fromJson(json, BookSource.class); |
||||
} catch (Exception ignored) { |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
public String getSourceName() { |
||||
return this.sourceName; |
||||
} |
||||
|
||||
public void setSourceName(String sourceName) { |
||||
this.sourceName = sourceName; |
||||
} |
||||
|
||||
public String getSourceGroup() { |
||||
return this.sourceGroup; |
||||
} |
||||
|
||||
public void setSourceGroup(String sourceGroup) { |
||||
this.sourceGroup = sourceGroup; |
||||
upGroupList(); |
||||
this.sourceGroup = TextUtils.join("; ", groupList); |
||||
} |
||||
|
||||
public String getSourceUrl() { |
||||
return this.sourceUrl; |
||||
} |
||||
|
||||
public void setSourceUrl(String sourceUrl) { |
||||
this.sourceUrl = sourceUrl; |
||||
} |
||||
|
||||
public String getSourceCharset() { |
||||
return this.sourceCharset; |
||||
} |
||||
|
||||
public void setSourceCharset(String sourceCharset) { |
||||
this.sourceCharset = sourceCharset; |
||||
} |
||||
|
||||
public int getOrderNum() { |
||||
return this.orderNum; |
||||
} |
||||
|
||||
public void setOrderNum(int orderNum) { |
||||
this.orderNum = orderNum; |
||||
} |
||||
|
||||
public int getWeight() { |
||||
return this.weight; |
||||
} |
||||
|
||||
public void setWeight(int weight) { |
||||
this.weight = weight; |
||||
} |
||||
|
||||
public boolean getEnable() { |
||||
return this.enable; |
||||
} |
||||
|
||||
public void setEnable(boolean enable) { |
||||
this.enable = enable; |
||||
} |
||||
|
||||
public SearchRule getSearchRule() { |
||||
return this.searchRule; |
||||
} |
||||
|
||||
public void setSearchRule(SearchRule searchRule) { |
||||
this.searchRule = searchRule; |
||||
} |
||||
|
||||
public InfoRule getInfoRule() { |
||||
return this.infoRule; |
||||
} |
||||
|
||||
public void setInfoRule(InfoRule infoRule) { |
||||
this.infoRule = infoRule; |
||||
} |
||||
|
||||
public TocRule getTocRule() { |
||||
return this.tocRule; |
||||
} |
||||
|
||||
public void setTocRule(TocRule tocRule) { |
||||
this.tocRule = tocRule; |
||||
} |
||||
|
||||
public ContentRule getContentRule() { |
||||
return this.contentRule; |
||||
} |
||||
|
||||
public void setContentRule(ContentRule contentRule) { |
||||
this.contentRule = contentRule; |
||||
} |
||||
|
||||
public String getSourceEName() { |
||||
return this.sourceEName; |
||||
} |
||||
|
||||
public void setSourceEName(String sourceEName) { |
||||
this.sourceEName = sourceEName; |
||||
} |
||||
|
||||
private void upGroupList() { |
||||
if (groupList == null) |
||||
groupList = new ArrayList<>(); |
||||
else |
||||
groupList.clear(); |
||||
if (!TextUtils.isEmpty(sourceGroup)) { |
||||
for (String group : sourceGroup.split("\\s*[,;,;]\\s*")) { |
||||
group = group.trim(); |
||||
if (TextUtils.isEmpty(group) || groupList.contains(group)) continue; |
||||
groupList.add(group); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void addGroup(String group) { |
||||
if (groupList == null) |
||||
upGroupList(); |
||||
if (!groupList.contains(group)) { |
||||
groupList.add(group); |
||||
updateModTime(); |
||||
sourceGroup = TextUtils.join("; ", groupList); |
||||
} |
||||
} |
||||
|
||||
public void removeGroup(String group) { |
||||
if (groupList == null) |
||||
upGroupList(); |
||||
if (groupList.contains(group)) { |
||||
groupList.remove(group); |
||||
updateModTime(); |
||||
sourceGroup = TextUtils.join("; ", groupList); |
||||
} |
||||
} |
||||
|
||||
public boolean containsGroup(String group) { |
||||
if (groupList == null) { |
||||
upGroupList(); |
||||
} |
||||
return groupList.contains(group); |
||||
} |
||||
|
||||
public Long getLastUpdateTime() { |
||||
return this.lastUpdateTime; |
||||
} |
||||
|
||||
public void setLastUpdateTime(Long lastUpdateTime) { |
||||
this.lastUpdateTime = lastUpdateTime; |
||||
} |
||||
|
||||
public void updateModTime() { |
||||
this.lastUpdateTime = System.currentTimeMillis(); |
||||
} |
||||
|
||||
public String getSourceComment() { |
||||
return this.sourceComment; |
||||
} |
||||
|
||||
public void setSourceComment(String sourceComment) { |
||||
this.sourceComment = sourceComment; |
||||
} |
||||
|
||||
public FindRule getFindRule() { |
||||
return this.findRule; |
||||
} |
||||
|
||||
public void setFindRule(FindRule findRule) { |
||||
this.findRule = findRule; |
||||
} |
||||
|
||||
public String getSourceType() { |
||||
return this.sourceType; |
||||
} |
||||
|
||||
public void setSourceType(String sourceType) { |
||||
this.sourceType = sourceType; |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 18:01 |
||||
*/ |
||||
public class ContentRule implements Parcelable { |
||||
private String content; |
||||
private String contentBaseUrl; |
||||
private String contentUrlNext; |
||||
|
||||
protected ContentRule(Parcel in) { |
||||
content = in.readString(); |
||||
contentBaseUrl = in.readString(); |
||||
contentUrlNext = in.readString(); |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeString(content); |
||||
dest.writeString(contentBaseUrl); |
||||
dest.writeString(contentUrlNext); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<ContentRule> CREATOR = new Creator<ContentRule>() { |
||||
@Override |
||||
public ContentRule createFromParcel(Parcel in) { |
||||
return new ContentRule(in); |
||||
} |
||||
|
||||
@Override |
||||
public ContentRule[] newArray(int size) { |
||||
return new ContentRule[size]; |
||||
} |
||||
}; |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null) o = new ContentRule(); |
||||
if (getClass() != o.getClass()) return false; |
||||
ContentRule that = (ContentRule) o; |
||||
return stringEquals(content, that.content) && |
||||
stringEquals(contentBaseUrl, that.contentBaseUrl) && |
||||
stringEquals(contentUrlNext, that.contentUrlNext); |
||||
} |
||||
|
||||
|
||||
public ContentRule() { |
||||
} |
||||
|
||||
public String getContent() { |
||||
return content; |
||||
} |
||||
|
||||
public void setContent(String content) { |
||||
this.content = content; |
||||
} |
||||
|
||||
public String getContentBaseUrl() { |
||||
return contentBaseUrl; |
||||
} |
||||
|
||||
public void setContentBaseUrl(String contentBaseUrl) { |
||||
this.contentBaseUrl = contentBaseUrl; |
||||
} |
||||
|
||||
public String getContentUrlNext() { |
||||
return contentUrlNext; |
||||
} |
||||
|
||||
public void setContentUrlNext(String contentUrlNext) { |
||||
this.contentUrlNext = contentUrlNext; |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,185 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
|
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 8:57 |
||||
*/ |
||||
public class FindRule implements Parcelable { |
||||
private String bookList; |
||||
private String name; |
||||
private String author; |
||||
private String type; |
||||
private String desc; |
||||
private String wordCount; |
||||
private String status; |
||||
private String lastChapter; |
||||
private String updateTime; |
||||
private String imgUrl; |
||||
private String tocUrl; |
||||
|
||||
public FindRule() { |
||||
} |
||||
|
||||
protected FindRule(Parcel in) { |
||||
bookList = in.readString(); |
||||
name = in.readString(); |
||||
author = in.readString(); |
||||
type = in.readString(); |
||||
desc = in.readString(); |
||||
wordCount = in.readString(); |
||||
status = in.readString(); |
||||
lastChapter = in.readString(); |
||||
updateTime = in.readString(); |
||||
imgUrl = in.readString(); |
||||
tocUrl = in.readString(); |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeString(bookList); |
||||
dest.writeString(name); |
||||
dest.writeString(author); |
||||
dest.writeString(type); |
||||
dest.writeString(desc); |
||||
dest.writeString(wordCount); |
||||
dest.writeString(status); |
||||
dest.writeString(lastChapter); |
||||
dest.writeString(updateTime); |
||||
dest.writeString(imgUrl); |
||||
dest.writeString(tocUrl); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<FindRule> CREATOR = new Creator<FindRule>() { |
||||
@Override |
||||
public FindRule createFromParcel(Parcel in) { |
||||
return new FindRule(in); |
||||
} |
||||
|
||||
@Override |
||||
public FindRule[] newArray(int size) { |
||||
return new FindRule[size]; |
||||
} |
||||
}; |
||||
|
||||
public String getBookList() { |
||||
return bookList; |
||||
} |
||||
|
||||
public void setBookList(String bookList) { |
||||
this.bookList = bookList; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getAuthor() { |
||||
return author; |
||||
} |
||||
|
||||
public void setAuthor(String author) { |
||||
this.author = author; |
||||
} |
||||
|
||||
public String getType() { |
||||
return type; |
||||
} |
||||
|
||||
public void setType(String type) { |
||||
this.type = type; |
||||
} |
||||
|
||||
public String getDesc() { |
||||
return desc; |
||||
} |
||||
|
||||
public void setDesc(String desc) { |
||||
this.desc = desc; |
||||
} |
||||
|
||||
public String getWordCount() { |
||||
return wordCount; |
||||
} |
||||
|
||||
public void setWordCount(String wordCount) { |
||||
this.wordCount = wordCount; |
||||
} |
||||
|
||||
public String getStatus() { |
||||
return status; |
||||
} |
||||
|
||||
public void setStatus(String status) { |
||||
this.status = status; |
||||
} |
||||
|
||||
public String getLastChapter() { |
||||
return lastChapter; |
||||
} |
||||
|
||||
public void setLastChapter(String lastChapter) { |
||||
this.lastChapter = lastChapter; |
||||
} |
||||
|
||||
public String getUpdateTime() { |
||||
return updateTime; |
||||
} |
||||
|
||||
public void setUpdateTime(String updateTime) { |
||||
this.updateTime = updateTime; |
||||
} |
||||
|
||||
public String getImgUrl() { |
||||
return imgUrl; |
||||
} |
||||
|
||||
public void setImgUrl(String imgUrl) { |
||||
this.imgUrl = imgUrl; |
||||
} |
||||
|
||||
public String getTocUrl() { |
||||
return tocUrl; |
||||
} |
||||
|
||||
public void setTocUrl(String tocUrl) { |
||||
this.tocUrl = tocUrl; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null) o = new FindRule(); |
||||
if (getClass() != o.getClass()) return false; |
||||
FindRule findRule = (FindRule) o; |
||||
return stringEquals(bookList, findRule.bookList) && |
||||
stringEquals(name, findRule.name) && |
||||
stringEquals(author, findRule.author) && |
||||
stringEquals(type, findRule.type) && |
||||
stringEquals(desc, findRule.desc) && |
||||
stringEquals(wordCount, findRule.wordCount) && |
||||
stringEquals(status, findRule.status) && |
||||
stringEquals(lastChapter, findRule.lastChapter) && |
||||
stringEquals(updateTime, findRule.updateTime) && |
||||
stringEquals(imgUrl, findRule.imgUrl) && |
||||
stringEquals(tocUrl, findRule.tocUrl); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,175 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 17:53 |
||||
*/ |
||||
public class InfoRule implements Parcelable { |
||||
private String name; |
||||
private String author; |
||||
private String type; |
||||
private String desc; |
||||
private String status; |
||||
private String wordCount; |
||||
private String lastChapter; |
||||
private String updateTime; |
||||
private String imgUrl; |
||||
private String tocUrl; |
||||
|
||||
public InfoRule() { |
||||
} |
||||
|
||||
protected InfoRule(Parcel in) { |
||||
name = in.readString(); |
||||
author = in.readString(); |
||||
type = in.readString(); |
||||
desc = in.readString(); |
||||
status = in.readString(); |
||||
wordCount = in.readString(); |
||||
lastChapter = in.readString(); |
||||
updateTime = in.readString(); |
||||
imgUrl = in.readString(); |
||||
tocUrl = in.readString(); |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeString(name); |
||||
dest.writeString(author); |
||||
dest.writeString(type); |
||||
dest.writeString(desc); |
||||
dest.writeString(status); |
||||
dest.writeString(wordCount); |
||||
dest.writeString(lastChapter); |
||||
dest.writeString(updateTime); |
||||
dest.writeString(imgUrl); |
||||
dest.writeString(tocUrl); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<InfoRule> CREATOR = new Creator<InfoRule>() { |
||||
@Override |
||||
public InfoRule createFromParcel(Parcel in) { |
||||
return new InfoRule(in); |
||||
} |
||||
|
||||
@Override |
||||
public InfoRule[] newArray(int size) { |
||||
return new InfoRule[size]; |
||||
} |
||||
}; |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null) o = new InfoRule(); |
||||
if (getClass() != o.getClass()) return false; |
||||
InfoRule infoRule = (InfoRule) o; |
||||
return stringEquals(name, infoRule.name) && |
||||
stringEquals(author, infoRule.author) && |
||||
stringEquals(type, infoRule.type) && |
||||
stringEquals(desc, infoRule.desc) && |
||||
stringEquals(status, infoRule.status) && |
||||
stringEquals(wordCount, infoRule.wordCount) && |
||||
stringEquals(lastChapter, infoRule.lastChapter) && |
||||
stringEquals(updateTime, infoRule.updateTime) && |
||||
stringEquals(imgUrl, infoRule.imgUrl) && |
||||
stringEquals(tocUrl, infoRule.tocUrl); |
||||
} |
||||
|
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getAuthor() { |
||||
return author; |
||||
} |
||||
|
||||
public void setAuthor(String author) { |
||||
this.author = author; |
||||
} |
||||
|
||||
public String getType() { |
||||
return type; |
||||
} |
||||
|
||||
public void setType(String type) { |
||||
this.type = type; |
||||
} |
||||
|
||||
public String getDesc() { |
||||
return desc; |
||||
} |
||||
|
||||
public void setDesc(String desc) { |
||||
this.desc = desc; |
||||
} |
||||
|
||||
public String getStatus() { |
||||
return status; |
||||
} |
||||
|
||||
public void setStatus(String status) { |
||||
this.status = status; |
||||
} |
||||
|
||||
public String getWordCount() { |
||||
return wordCount; |
||||
} |
||||
|
||||
public void setWordCount(String wordCount) { |
||||
this.wordCount = wordCount; |
||||
} |
||||
|
||||
public String getLastChapter() { |
||||
return lastChapter; |
||||
} |
||||
|
||||
public void setLastChapter(String lastChapter) { |
||||
this.lastChapter = lastChapter; |
||||
} |
||||
|
||||
public String getUpdateTime() { |
||||
return updateTime; |
||||
} |
||||
|
||||
public void setUpdateTime(String updateTime) { |
||||
this.updateTime = updateTime; |
||||
} |
||||
|
||||
public String getImgUrl() { |
||||
return imgUrl; |
||||
} |
||||
|
||||
public void setImgUrl(String imgUrl) { |
||||
this.imgUrl = imgUrl; |
||||
} |
||||
|
||||
public String getTocUrl() { |
||||
return tocUrl; |
||||
} |
||||
|
||||
public void setTocUrl(String tocUrl) { |
||||
this.tocUrl = tocUrl; |
||||
} |
||||
} |
@ -0,0 +1,236 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
import xyz.fycz.myreader.util.utils.GsonUtils; |
||||
|
||||
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 17:48 |
||||
*/ |
||||
public class SearchRule implements Parcelable { |
||||
private String searchUrl; |
||||
private String charset; |
||||
private String list; |
||||
private String name; |
||||
private String author; |
||||
private String type; |
||||
private String desc; |
||||
private String wordCount; |
||||
private String status; |
||||
private String lastChapter; |
||||
private String updateTime; |
||||
private String imgUrl; |
||||
private String tocUrl; |
||||
private String infoUrl; |
||||
private boolean relatedWithInfo; |
||||
|
||||
public SearchRule() { |
||||
} |
||||
|
||||
protected SearchRule(Parcel in) { |
||||
searchUrl = in.readString(); |
||||
charset = in.readString(); |
||||
list = in.readString(); |
||||
name = in.readString(); |
||||
author = in.readString(); |
||||
type = in.readString(); |
||||
desc = in.readString(); |
||||
wordCount = in.readString(); |
||||
status = in.readString(); |
||||
lastChapter = in.readString(); |
||||
updateTime = in.readString(); |
||||
imgUrl = in.readString(); |
||||
tocUrl = in.readString(); |
||||
infoUrl = in.readString(); |
||||
relatedWithInfo = in.readByte() != 0; |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeString(searchUrl); |
||||
dest.writeString(charset); |
||||
dest.writeString(list); |
||||
dest.writeString(name); |
||||
dest.writeString(author); |
||||
dest.writeString(type); |
||||
dest.writeString(desc); |
||||
dest.writeString(wordCount); |
||||
dest.writeString(status); |
||||
dest.writeString(lastChapter); |
||||
dest.writeString(updateTime); |
||||
dest.writeString(imgUrl); |
||||
dest.writeString(tocUrl); |
||||
dest.writeString(infoUrl); |
||||
dest.writeByte((byte) (relatedWithInfo ? 1 : 0)); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<SearchRule> CREATOR = new Creator<SearchRule>() { |
||||
@Override |
||||
public SearchRule createFromParcel(Parcel in) { |
||||
return new SearchRule(in); |
||||
} |
||||
|
||||
@Override |
||||
public SearchRule[] newArray(int size) { |
||||
return new SearchRule[size]; |
||||
} |
||||
}; |
||||
|
||||
public String getSearchUrl() { |
||||
return searchUrl; |
||||
} |
||||
|
||||
public void setSearchUrl(String searchUrl) { |
||||
this.searchUrl = searchUrl; |
||||
} |
||||
|
||||
public String getCharset() { |
||||
return charset; |
||||
} |
||||
|
||||
public void setCharset(String charset) { |
||||
this.charset = charset; |
||||
} |
||||
|
||||
public String getList() { |
||||
return list; |
||||
} |
||||
|
||||
public void setList(String list) { |
||||
this.list = list; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getAuthor() { |
||||
return author; |
||||
} |
||||
|
||||
public void setAuthor(String author) { |
||||
this.author = author; |
||||
} |
||||
|
||||
public String getType() { |
||||
return type; |
||||
} |
||||
|
||||
public void setType(String type) { |
||||
this.type = type; |
||||
} |
||||
|
||||
public String getDesc() { |
||||
return desc; |
||||
} |
||||
|
||||
public void setDesc(String desc) { |
||||
this.desc = desc; |
||||
} |
||||
|
||||
public String getLastChapter() { |
||||
return lastChapter; |
||||
} |
||||
|
||||
public void setLastChapter(String lastChapter) { |
||||
this.lastChapter = lastChapter; |
||||
} |
||||
|
||||
public String getUpdateTime() { |
||||
return updateTime; |
||||
} |
||||
|
||||
public void setUpdateTime(String updateTime) { |
||||
this.updateTime = updateTime; |
||||
} |
||||
|
||||
public String getImgUrl() { |
||||
return imgUrl; |
||||
} |
||||
|
||||
public void setImgUrl(String imgUrl) { |
||||
this.imgUrl = imgUrl; |
||||
} |
||||
|
||||
public String getTocUrl() { |
||||
return tocUrl; |
||||
} |
||||
|
||||
public void setTocUrl(String tocUrl) { |
||||
this.tocUrl = tocUrl; |
||||
} |
||||
|
||||
public String getWordCount() { |
||||
return wordCount; |
||||
} |
||||
|
||||
public void setWordCount(String wordCount) { |
||||
this.wordCount = wordCount; |
||||
} |
||||
|
||||
public String getStatus() { |
||||
return status; |
||||
} |
||||
|
||||
public void setStatus(String status) { |
||||
this.status = status; |
||||
} |
||||
|
||||
public String getInfoUrl() { |
||||
return infoUrl; |
||||
} |
||||
|
||||
public void setInfoUrl(String infoUrl) { |
||||
this.infoUrl = infoUrl; |
||||
} |
||||
|
||||
public boolean isRelatedWithInfo() { |
||||
return relatedWithInfo; |
||||
} |
||||
|
||||
public void setRelatedWithInfo(boolean relatedWithInfo) { |
||||
this.relatedWithInfo = relatedWithInfo; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
SearchRule that = (SearchRule) o; |
||||
return relatedWithInfo == that.relatedWithInfo && |
||||
stringEquals(searchUrl, that.searchUrl) && |
||||
stringEquals(charset, that.charset) && |
||||
stringEquals(list, that.list) && |
||||
stringEquals(name, that.name) && |
||||
stringEquals(author, that.author) && |
||||
stringEquals(type, that.type) && |
||||
stringEquals(desc, that.desc) && |
||||
stringEquals(wordCount, that.wordCount) && |
||||
stringEquals(status, that.status) && |
||||
stringEquals(lastChapter, that.lastChapter) && |
||||
stringEquals(updateTime, that.updateTime) && |
||||
stringEquals(imgUrl, that.imgUrl) && |
||||
stringEquals(tocUrl, that.tocUrl) && |
||||
stringEquals(infoUrl, that.infoUrl); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,116 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule; |
||||
|
||||
import android.os.Parcel; |
||||
import android.os.Parcelable; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 17:59 |
||||
*/ |
||||
public class TocRule implements Parcelable { |
||||
private String chapterList; |
||||
private String chapterBaseUrl; |
||||
private String chapterName; |
||||
private String chapterUrl; |
||||
private String tocUrlNext; |
||||
|
||||
protected TocRule(Parcel in) { |
||||
chapterList = in.readString(); |
||||
chapterBaseUrl = in.readString(); |
||||
chapterName = in.readString(); |
||||
chapterUrl = in.readString(); |
||||
tocUrlNext = in.readString(); |
||||
} |
||||
|
||||
@Override |
||||
public void writeToParcel(Parcel dest, int flags) { |
||||
dest.writeString(chapterList); |
||||
dest.writeString(chapterBaseUrl); |
||||
dest.writeString(chapterName); |
||||
dest.writeString(chapterUrl); |
||||
dest.writeString(tocUrlNext); |
||||
} |
||||
|
||||
@Override |
||||
public int describeContents() { |
||||
return 0; |
||||
} |
||||
|
||||
public static final Creator<TocRule> CREATOR = new Creator<TocRule>() { |
||||
@Override |
||||
public TocRule createFromParcel(Parcel in) { |
||||
return new TocRule(in); |
||||
} |
||||
|
||||
@Override |
||||
public TocRule[] newArray(int size) { |
||||
return new TocRule[size]; |
||||
} |
||||
}; |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null) o = new TocRule(); |
||||
if (getClass() != o.getClass()) return false; |
||||
TocRule tocRule = (TocRule) o; |
||||
return stringEquals(chapterList, tocRule.chapterList) && |
||||
stringEquals(chapterBaseUrl, tocRule.chapterBaseUrl) && |
||||
stringEquals(chapterName, tocRule.chapterName) && |
||||
stringEquals(chapterUrl, tocRule.chapterUrl) && |
||||
stringEquals(tocUrlNext, tocRule.tocUrlNext); |
||||
} |
||||
|
||||
public String getChapterList() { |
||||
return chapterList; |
||||
} |
||||
|
||||
public void setChapterList(String chapterList) { |
||||
this.chapterList = chapterList; |
||||
} |
||||
|
||||
public String getChapterBaseUrl() { |
||||
return chapterBaseUrl; |
||||
} |
||||
|
||||
public void setChapterBaseUrl(String chapterBaseUrl) { |
||||
this.chapterBaseUrl = chapterBaseUrl; |
||||
} |
||||
|
||||
public String getChapterName() { |
||||
return chapterName; |
||||
} |
||||
|
||||
public void setChapterName(String chapterName) { |
||||
this.chapterName = chapterName; |
||||
} |
||||
|
||||
public String getChapterUrl() { |
||||
return chapterUrl; |
||||
} |
||||
|
||||
public void setChapterUrl(String chapterUrl) { |
||||
this.chapterUrl = chapterUrl; |
||||
} |
||||
|
||||
public String getTocUrlNext() { |
||||
return tocUrlNext; |
||||
} |
||||
|
||||
public void setTocUrlNext(String tocUrlNext) { |
||||
this.tocUrlNext = tocUrlNext; |
||||
} |
||||
|
||||
public TocRule() { |
||||
} |
||||
|
||||
} |
@ -0,0 +1,22 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule.convert; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.ContentRule; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 18:27 |
||||
*/ |
||||
public class ContentRuleConvert implements PropertyConverter<ContentRule, String> { |
||||
@Override |
||||
public ContentRule convertToEntityProperty(String databaseValue) { |
||||
return GsonExtensionsKt.getGSON().fromJson(databaseValue, ContentRule.class); |
||||
} |
||||
|
||||
@Override |
||||
public String convertToDatabaseValue(ContentRule entityProperty) { |
||||
return GsonExtensionsKt.getGSON().toJson(entityProperty); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule.convert; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.FindRule; |
||||
import xyz.fycz.myreader.greendao.entity.rule.TocRule; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 18:28 |
||||
*/ |
||||
public class FindRuleConvert implements PropertyConverter<FindRule, String> { |
||||
|
||||
@Override |
||||
public FindRule convertToEntityProperty(String databaseValue) { |
||||
return GsonExtensionsKt.getGSON().fromJson(databaseValue, FindRule.class); |
||||
} |
||||
|
||||
@Override |
||||
public String convertToDatabaseValue(FindRule entityProperty) { |
||||
return GsonExtensionsKt.getGSON().toJson(entityProperty); |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule.convert; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.InfoRule; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 18:28 |
||||
*/ |
||||
public class InfoRuleConvert implements PropertyConverter<InfoRule, String> { |
||||
|
||||
@Override |
||||
public InfoRule convertToEntityProperty(String databaseValue) { |
||||
return GsonExtensionsKt.getGSON().fromJson(databaseValue, InfoRule.class); |
||||
} |
||||
|
||||
@Override |
||||
public String convertToDatabaseValue(InfoRule entityProperty) { |
||||
return GsonExtensionsKt.getGSON().toJson(entityProperty); |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule.convert; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.SearchRule; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 18:29 |
||||
*/ |
||||
public class SearchRuleConvert implements PropertyConverter<SearchRule, String> { |
||||
|
||||
@Override |
||||
public SearchRule convertToEntityProperty(String databaseValue) { |
||||
return GsonExtensionsKt.getGSON().fromJson(databaseValue, SearchRule.class); |
||||
} |
||||
|
||||
@Override |
||||
public String convertToDatabaseValue(SearchRule entityProperty) { |
||||
return GsonExtensionsKt.getGSON().toJson(entityProperty); |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package xyz.fycz.myreader.greendao.entity.rule.convert; |
||||
|
||||
import org.greenrobot.greendao.converter.PropertyConverter; |
||||
|
||||
import xyz.fycz.myreader.greendao.entity.rule.TocRule; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/8 18:28 |
||||
*/ |
||||
public class TocRuleConvert implements PropertyConverter<TocRule, String> { |
||||
|
||||
@Override |
||||
public TocRule convertToEntityProperty(String databaseValue) { |
||||
return GsonExtensionsKt.getGSON().fromJson(databaseValue, TocRule.class); |
||||
} |
||||
|
||||
@Override |
||||
public String convertToDatabaseValue(TocRule entityProperty) { |
||||
return GsonExtensionsKt.getGSON().toJson(entityProperty); |
||||
} |
||||
} |
@ -0,0 +1,247 @@ |
||||
package xyz.fycz.myreader.model.source; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
import xyz.fycz.myreader.util.utils.StringUtils; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/14 18:51 |
||||
*/ |
||||
public abstract class BaseAnalyzer { |
||||
|
||||
public abstract List<String> getStringList(String rule, Object obj, boolean first); |
||||
|
||||
public List<String> getStringList(String rule, Object obj) { |
||||
return getStringList(rule, obj, false); |
||||
} |
||||
|
||||
public String getString(String rule, Object obj) { |
||||
if (StringHelper.isEmpty(rule)) return ""; |
||||
List<String> list = getStringList(rule, obj, true); |
||||
if (list.size() == 0) return ""; |
||||
return list.get(0); |
||||
} |
||||
|
||||
/** |
||||
* 执行函数 |
||||
* 1、@r/@replace(oldStr, newStr) |
||||
* 2、@a/@append(str1+str2+str3) |
||||
* 3、@e/equal(str1,str2) 如:@e(<text>, novel) |
||||
* 4、@c/contains(str) 必须包含 |
||||
* 5、@nc/notContains(str) 必须不包含 |
||||
* |
||||
* @param funs |
||||
* @param content |
||||
* @return |
||||
*/ |
||||
protected String evalFunction(String funs, String content) { |
||||
boolean hasSemicolon = funs.contains("\\;"); |
||||
if (hasSemicolon) funs = funs.replace("\\;", "\\;"); |
||||
if (funs.endsWith(";")) funs = funs.substring(0, funs.length() - 1); |
||||
String[] funArr = funs.split(";"); |
||||
for (String fun : funArr) { |
||||
if (hasSemicolon) fun = fun.replace("\\;", ";"); |
||||
if (fun.contains("@e")) { |
||||
content = String.valueOf(evalEqual(fun, content)); |
||||
} else if (fun.contains("@r")) { |
||||
content = evalReplace(fun, content); |
||||
} else if (fun.contains("@a")) { |
||||
content = evalAppend(fun, content); |
||||
} else if (fun.contains("@c")) { |
||||
content = evalContains(fun, content); |
||||
} else if (fun.contains("@nc")) { |
||||
content = evalNotContains(fun, content); |
||||
} |
||||
} |
||||
return content; |
||||
} |
||||
|
||||
/** |
||||
* 执行替换函数 |
||||
* |
||||
* @param rule |
||||
* @param content |
||||
* @return |
||||
*/ |
||||
protected String evalReplace(String rule, String content) { |
||||
rule = rule.trim(); |
||||
if (!rule.matches("@(r|replace)\\(.+,.*\\)")) { |
||||
Log.d("evalReplace", "格式错误"); |
||||
return content; |
||||
} |
||||
rule = rule.replace("(*)", ".*"); |
||||
rule = rule.replace("\\,", "\\,"); |
||||
String oldStr = null; |
||||
String newStr = null; |
||||
try { |
||||
oldStr = StringUtils.getSubString(rule, "(", ",") |
||||
.replace("\\,", ","); |
||||
newStr = StringUtils.getSubString(rule, ",", ")") |
||||
.replace("\\,", ","); |
||||
Log.d("evalReplace", "执行成功"); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
return content; |
||||
} |
||||
return content.replaceAll(oldStr, newStr); |
||||
} |
||||
|
||||
/** |
||||
* 执行添加函数 |
||||
* |
||||
* @param rule |
||||
* @param content |
||||
* @return |
||||
*/ |
||||
protected String evalAppend(String rule, String content) { |
||||
rule = rule.trim(); |
||||
if (!rule.matches("@(a|append)\\(.+\\)")) { |
||||
Log.d("evalAppend", "格式错误"); |
||||
return content; |
||||
} |
||||
StringBuilder reVal = null; |
||||
try { |
||||
rule = StringUtils.getMSubString(rule, "(", ")"); |
||||
rule = rule.replace("\\+", "\\plus"); |
||||
String[] strs = rule.split("\\+"); |
||||
reVal = new StringBuilder(); |
||||
for (String str : strs) { |
||||
str = str.replace("\\plus", "+"); |
||||
if (str.matches("<(html|text)>")) { |
||||
reVal.append(content); |
||||
} else { |
||||
reVal.append(str); |
||||
} |
||||
} |
||||
Log.d("evalAppend", "执行成功"); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
return content; |
||||
} |
||||
return reVal.toString(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 执行判断是否相等 |
||||
* |
||||
* @param rule |
||||
* @return |
||||
*/ |
||||
protected boolean evalEqual(String rule, String content) { |
||||
rule = rule.trim(); |
||||
if (!rule.matches("@(e|equal)\\(.*,.*\\)")) { |
||||
Log.d("evalEqual", "格式错误"); |
||||
return false; |
||||
} |
||||
String lStr = null; |
||||
String rStr = null; |
||||
try { |
||||
lStr = StringUtils.getSubString(rule, "(", ","); |
||||
rStr = StringUtils.getSubString(rule, ",", ")"); |
||||
if (lStr.matches("<(html|text)>")) { |
||||
lStr = content; |
||||
} |
||||
if (rStr.matches("<(html|text)>")) { |
||||
rStr = content; |
||||
} |
||||
Log.d("evalEqual", "执行成功"); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
return false; |
||||
} |
||||
return lStr != null && lStr.equals(rStr); |
||||
} |
||||
|
||||
/** |
||||
* 执行是否包含函数 |
||||
* |
||||
* @param rule |
||||
* @param content |
||||
* @return |
||||
*/ |
||||
protected String evalContains(String rule, String content) { |
||||
rule = rule.trim(); |
||||
if (!rule.matches("@(c|contains)\\(.+\\)")) { |
||||
Log.d("evalContains", "格式错误"); |
||||
return content; |
||||
} |
||||
try { |
||||
rule = StringUtils.getMSubString(rule, "(", ")"); |
||||
if (!content.contains(rule)) { |
||||
content = ""; |
||||
} |
||||
Log.d("evalContains", "执行成功"); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
return content; |
||||
} |
||||
return content; |
||||
} |
||||
|
||||
/** |
||||
* 执行是否不包含函数 |
||||
* |
||||
* @param rule |
||||
* @param content |
||||
* @return |
||||
*/ |
||||
protected String evalNotContains(String rule, String content) { |
||||
rule = rule.trim(); |
||||
if (!rule.matches("@(nc|notContains)\\(.+\\)")) { |
||||
Log.d("evalNotContains", "格式错误"); |
||||
return content; |
||||
} |
||||
try { |
||||
rule = StringUtils.getMSubString(rule, "(", ")"); |
||||
if (content.contains(rule)) { |
||||
content = ""; |
||||
} |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
return content; |
||||
} |
||||
Log.d("evalNotContains", "执行成功"); |
||||
return content; |
||||
} |
||||
|
||||
/** |
||||
* 执行跳过函数 |
||||
* |
||||
* @param rule |
||||
* @param content |
||||
* @return |
||||
*/ |
||||
private String evalSkip(String rule, String content, int index) { |
||||
rule = rule.trim(); |
||||
if (!rule.matches("@(s|skip)\\([0-9]+\\)") && !rule.matches("!([0-9]+)")) { |
||||
Log.d("evalSkip", "格式错误"); |
||||
return content; |
||||
} |
||||
if (rule.matches("@(s|skip)\\([0-9]+\\)")) { |
||||
rule = StringUtils.getMSubString(rule, "(", ")"); |
||||
} else if (rule.matches("!([0-9]+)")) { |
||||
rule = rule.substring(1); |
||||
} |
||||
int skip = 0; |
||||
try { |
||||
skip = Integer.parseInt(rule); |
||||
Log.d("evalSkip", "执行成功"); |
||||
if (index < skip) return ""; |
||||
} catch (NumberFormatException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return content; |
||||
} |
||||
|
||||
public static String getCharset(String charset) { |
||||
if (StringHelper.isEmpty(charset)) { |
||||
charset = "UTF-8"; |
||||
} |
||||
return charset; |
||||
} |
||||
} |
@ -0,0 +1,375 @@ |
||||
package xyz.fycz.myreader.model.source; |
||||
|
||||
import android.database.Cursor; |
||||
import android.text.TextUtils; |
||||
import android.util.Log; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.ObservableEmitter; |
||||
import io.reactivex.Single; |
||||
import io.reactivex.SingleOnSubscribe; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.App; |
||||
import xyz.fycz.myreader.enums.LocalBookSource; |
||||
import xyz.fycz.myreader.greendao.GreenDaoManager; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.greendao.gen.BookSourceDao; |
||||
import xyz.fycz.myreader.util.SharedPreUtils; |
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
import xyz.fycz.myreader.util.utils.GsonUtils; |
||||
import xyz.fycz.myreader.util.utils.NetworkUtils; |
||||
import xyz.fycz.myreader.util.utils.OkHttpUtils; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.util.utils.StringUtils; |
||||
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; |
||||
|
||||
|
||||
public class BookSourceManager { |
||||
|
||||
public static List<BookSource> getEnabledBookSource() { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.Enable.eq(true)) |
||||
.orderRaw(BookSourceDao.Properties.Weight.columnName + " DESC") |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
|
||||
public static List<BookSource> getAllBookSource() { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.orderRaw(getBookSourceSort()) |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
|
||||
public static List<BookSource> getEnabledBookSourceByOrderNum() { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.Enable.eq(true)) |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
|
||||
public static List<BookSource> getAllBookSourceByOrderNum() { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
|
||||
public static List<BookSource> getEnableSourceByGroup(String group) { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.Enable.eq(true)) |
||||
.where(BookSourceDao.Properties.SourceGroup.like("%" + group + "%")) |
||||
.orderRaw(BookSourceDao.Properties.Weight.columnName + " DESC") |
||||
.list(); |
||||
} |
||||
|
||||
/** |
||||
* 通过book.getSource()获取书源 |
||||
* |
||||
* @param str |
||||
* @return |
||||
*/ |
||||
public static BookSource getBookSourceByStr(String str) { |
||||
if (NetworkUtils.isUrl(str)) { |
||||
return getBookSourceByUrl(str); |
||||
} else { |
||||
return getBookSourceByEName(str); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 通过url获取书源 |
||||
* |
||||
* @param url |
||||
* @return |
||||
*/ |
||||
public static BookSource getBookSourceByUrl(String url) { |
||||
if (url == null) return getDefaultSource(); |
||||
BookSource source = GreenDaoManager.getDaoSession().getBookSourceDao().load(url); |
||||
if (source == null) return getDefaultSource(); |
||||
return source; |
||||
} |
||||
|
||||
/** |
||||
* 内置书源获取 |
||||
* |
||||
* @param ename |
||||
* @return |
||||
*/ |
||||
@Nullable |
||||
public static BookSource getBookSourceByEName(String ename) { |
||||
if (ename == null) return getDefaultSource(); |
||||
if ("local".equals(ename)) return getLocalSource(); |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao(). |
||||
queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceEName.eq(ename)) |
||||
.unique(); |
||||
} |
||||
|
||||
/** |
||||
* 通过book.getSource()获取书源名 |
||||
* |
||||
* @param str |
||||
* @return |
||||
*/ |
||||
public static String getSourceNameByStr(String str) { |
||||
return getBookSourceByStr(str).getSourceName(); |
||||
} |
||||
|
||||
/** |
||||
* 获取默认书源 |
||||
* |
||||
* @return |
||||
*/ |
||||
private static BookSource getDefaultSource() { |
||||
BookSource bookSource = new BookSource(); |
||||
bookSource.setSourceUrl("xyz.fycz.myreader.webapi.crawler.read.FYReadCrawler"); |
||||
bookSource.setSourceName("风月小说"); |
||||
bookSource.setSourceEName("fynovel"); |
||||
bookSource.setSourceGroup("内置书源"); |
||||
return bookSource; |
||||
} |
||||
|
||||
/** |
||||
* 获取本地书籍 |
||||
* |
||||
* @return |
||||
*/ |
||||
private static BookSource getLocalSource() { |
||||
BookSource bookSource = new BookSource(); |
||||
bookSource.setSourceEName("local"); |
||||
bookSource.setSourceName("本地书籍"); |
||||
return bookSource; |
||||
} |
||||
|
||||
/** |
||||
* 获取所有内置书源 |
||||
* |
||||
* @return |
||||
*/ |
||||
public static List<BookSource> getAllLocalSource() { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceEName.isNotNull()) |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
|
||||
/** |
||||
* 获取所有导入书源 |
||||
* |
||||
* @return |
||||
*/ |
||||
public static List<BookSource> getAllNoLocalSource() { |
||||
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceEName.isNull()) |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
|
||||
/** |
||||
* 删除书源 |
||||
* |
||||
* @param source |
||||
*/ |
||||
public static void removeBookSource(BookSource source) { |
||||
if (source == null) return; |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().delete(source); |
||||
} |
||||
|
||||
public static void removeBookSources(List<BookSource> sources) { |
||||
if (sources == null) return; |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().deleteInTx(sources); |
||||
} |
||||
|
||||
|
||||
public static String getBookSourceSort() { |
||||
switch (SharedPreUtils.getInstance().getInt("SourceSort", 0)) { |
||||
case 1: |
||||
return BookSourceDao.Properties.Weight.columnName + " DESC"; |
||||
case 2: |
||||
return BookSourceDao.Properties.SourceName.columnName + " COLLATE LOCALIZED ASC"; |
||||
default: |
||||
return BookSourceDao.Properties.OrderNum.columnName + " ASC"; |
||||
} |
||||
} |
||||
|
||||
public static void addBookSource(List<BookSource> bookSources) { |
||||
for (BookSource bookSource : bookSources) { |
||||
addBookSource(bookSource); |
||||
} |
||||
} |
||||
|
||||
public static boolean addBookSource(BookSource bookSource) { |
||||
if (TextUtils.isEmpty(bookSource.getSourceName()) || TextUtils.isEmpty(bookSource.getSourceUrl())) |
||||
return false; |
||||
if (bookSource.getSourceUrl().endsWith("/")) { |
||||
bookSource.setSourceUrl(bookSource.getSourceUrl().replaceAll("/+$", "")); |
||||
} |
||||
BookSource temp = GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceUrl.eq(bookSource.getSourceUrl())).unique(); |
||||
if (temp != null) { |
||||
bookSource.setOrderNum(temp.getOrderNum()); |
||||
} else { |
||||
bookSource.setOrderNum((int) (GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder().count() + 1)); |
||||
} |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplace(bookSource); |
||||
return true; |
||||
} |
||||
|
||||
public static Single<Boolean> saveData(BookSource bookSource) { |
||||
return Single.create((SingleOnSubscribe<Boolean>) emitter -> { |
||||
if (bookSource.getOrderNum() == 0) { |
||||
bookSource.setOrderNum((int) (GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder().count() + 1)); |
||||
} |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplace(bookSource); |
||||
emitter.onSuccess(true); |
||||
}).compose(RxUtils::toSimpleSingle); |
||||
} |
||||
|
||||
public static Single<Boolean> saveDatas(List<BookSource> sources) { |
||||
return Single.create((SingleOnSubscribe<Boolean>) emitter -> { |
||||
for (BookSource source : sources) { |
||||
if (source.getOrderNum() == 0) { |
||||
source.setOrderNum((int) (GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder().count() + 1)); |
||||
} |
||||
} |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplaceInTx(sources); |
||||
emitter.onSuccess(true); |
||||
}).compose(RxUtils::toSimpleSingle); |
||||
} |
||||
|
||||
public static Single<Boolean> toTop(BookSource source) { |
||||
return Single.create((SingleOnSubscribe<Boolean>) e -> { |
||||
List<BookSource> List = getAllBookSourceByOrderNum(); |
||||
for (int i = 0; i < List.size(); i++) { |
||||
List.get(i).setOrderNum(i + 1); |
||||
} |
||||
source.setOrderNum(0); |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplaceInTx(List); |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplace(source); |
||||
e.onSuccess(true); |
||||
}).compose(RxUtils::toSimpleSingle); |
||||
} |
||||
|
||||
public static List<String> getEnableNoLocalGroupList() { |
||||
List<String> groupList = new ArrayList<>(); |
||||
String sql = "SELECT DISTINCT " |
||||
+ BookSourceDao.Properties.SourceGroup.columnName |
||||
+ " FROM " + BookSourceDao.TABLENAME |
||||
+ " WHERE " + BookSourceDao.Properties.Enable.name + " = 1"; |
||||
Cursor cursor = GreenDaoManager.getDaoSession().getDatabase().rawQuery(sql, null); |
||||
if (!cursor.moveToFirst()) return groupList; |
||||
do { |
||||
String group = cursor.getString(0); |
||||
if (TextUtils.isEmpty(group) || TextUtils.isEmpty(group.trim())) continue; |
||||
for (String item : group.split("\\s*[,;,;]\\s*")) { |
||||
if (TextUtils.isEmpty(item) || groupList.contains(item)|| item.equals("内置书源")) continue; |
||||
groupList.add(item); |
||||
} |
||||
} while (cursor.moveToNext()); |
||||
Collections.sort(groupList); |
||||
return groupList; |
||||
} |
||||
|
||||
public static List<String> getNoLocalGroupList() { |
||||
List<String> groupList = new ArrayList<>(); |
||||
String sql = "SELECT DISTINCT " + BookSourceDao.Properties.SourceGroup.columnName + " FROM " + BookSourceDao.TABLENAME; |
||||
Cursor cursor = GreenDaoManager.getDaoSession().getDatabase().rawQuery(sql, null); |
||||
if (!cursor.moveToFirst()) return groupList; |
||||
do { |
||||
String group = cursor.getString(0); |
||||
if (TextUtils.isEmpty(group) || TextUtils.isEmpty(group.trim())) continue; |
||||
for (String item : group.split("\\s*[,;,;]\\s*")) { |
||||
if (TextUtils.isEmpty(item) || groupList.contains(item) || item.equals("内置书源")) continue; |
||||
groupList.add(item); |
||||
} |
||||
} while (cursor.moveToNext()); |
||||
Collections.sort(groupList); |
||||
return groupList; |
||||
} |
||||
|
||||
public static Observable<List<BookSource>> importSource(String string) { |
||||
if (StringHelper.isEmpty(string)) return null; |
||||
string = string.trim(); |
||||
if (NetworkUtils.isIPv4Address(string)) { |
||||
string = String.format("http://%s:65501", string); |
||||
} |
||||
if (StringUtils.isJsonType(string)) { |
||||
return importBookSourceFromJson(string.trim()) |
||||
.compose(RxUtils::toSimpleSingle); |
||||
} else if (StringUtils.isCompressJsonType(string)) { |
||||
return importBookSourceFromJson(StringUtils.unCompressJson(string)) |
||||
.compose(RxUtils::toSimpleSingle); |
||||
} |
||||
if (NetworkUtils.isUrl(string)) { |
||||
String finalString = string; |
||||
return Observable.create((ObservableEmitter<String> e) -> e.onNext(OkHttpUtils.getHtml(finalString))) |
||||
.flatMap(BookSourceManager::importBookSourceFromJson) |
||||
.compose(RxUtils::toSimpleSingle); |
||||
} |
||||
return Observable.error(new Exception("不是Json或Url格式")); |
||||
} |
||||
|
||||
private static Observable<List<BookSource>> importBookSourceFromJson(String json) { |
||||
return Observable.create(e -> { |
||||
List<BookSource> successImportSources = new ArrayList<>(); |
||||
if (StringUtils.isJsonArray(json)) { |
||||
try { |
||||
List<BookSource> bookSources = GsonUtils.parseJArray(json, BookSource.class); |
||||
for (BookSource bookSource : bookSources) { |
||||
if (bookSource.containsGroup("删除")) { |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceUrl.eq(bookSource.getSourceUrl())) |
||||
.buildDelete().executeDeleteWithoutDetachingEntities(); |
||||
} else { |
||||
if (addBookSource(bookSource)){ |
||||
successImportSources.add(bookSource); |
||||
} |
||||
} |
||||
} |
||||
e.onNext(successImportSources); |
||||
e.onComplete(); |
||||
return; |
||||
} catch (Exception ignored) { |
||||
} |
||||
} |
||||
if (StringUtils.isJsonObject(json)) { |
||||
try { |
||||
BookSource bookSource = GsonUtils.parseJObject(json, BookSource.class); |
||||
if (addBookSource(bookSource)) |
||||
successImportSources.add(bookSource); |
||||
e.onNext(successImportSources); |
||||
e.onComplete(); |
||||
return; |
||||
} catch (Exception ignored) { |
||||
} |
||||
} |
||||
e.onError(new Throwable("格式不对")); |
||||
}); |
||||
} |
||||
|
||||
|
||||
public static void initDefaultSources() { |
||||
Log.d("initDefaultSources", "execute"); |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().deleteAll(); |
||||
String searchSource = SharedPreUtils.getInstance().getString(App.getmContext().getString(R.string.searchSource)); |
||||
boolean isEmpty = StringHelper.isEmpty(searchSource); |
||||
for (LocalBookSource source : LocalBookSource.values()) { |
||||
if (source == LocalBookSource.local || source == LocalBookSource.fynovel) continue; |
||||
BookSource source1 = new BookSource(); |
||||
source1.setSourceEName(source.toString()); |
||||
source1.setSourceName(source.text); |
||||
source1.setSourceGroup("内置书源"); |
||||
source1.setEnable(isEmpty || searchSource.contains(source.toString())); |
||||
source1.setSourceUrl(ReadCrawlerUtil.getReadCrawlerClz(source.toString())); |
||||
BookSourceManager.addBookSource(source1); |
||||
} |
||||
BookSourceManager.addBookSource(MatcherAnalyzer.getTestSource()); |
||||
BookSourceManager.addBookSource(MatcherAnalyzer.getTestSource2()); |
||||
} |
||||
} |
@ -0,0 +1,84 @@ |
||||
package xyz.fycz.myreader.model.source; |
||||
|
||||
import com.google.gson.JsonArray; |
||||
import com.google.gson.JsonElement; |
||||
import com.jayway.jsonpath.Configuration; |
||||
import com.jayway.jsonpath.JsonPath; |
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.ParseContext; |
||||
import com.jayway.jsonpath.ReadContext; |
||||
import com.jayway.jsonpath.spi.json.GsonJsonProvider; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/15 9:08 |
||||
*/ |
||||
public class JsonPathAnalyzer extends BaseAnalyzer { |
||||
|
||||
private final ParseContext parseContext = JsonPath.using(Configuration.builder() |
||||
.jsonProvider(new GsonJsonProvider()) |
||||
.options(Option.ALWAYS_RETURN_LIST) |
||||
.build()); |
||||
|
||||
@Override |
||||
public List<String> getStringList(String rule, Object obj, boolean isFirst) { |
||||
return getStringList(rule, getReadContext(obj), isFirst); |
||||
} |
||||
|
||||
public List<String> getStringList(String rule, ReadContext rc, boolean isFirst) { |
||||
List<String> list = new ArrayList<>(); |
||||
if (StringHelper.isEmpty(rule)) return list; |
||||
boolean hasFunction = rule.contains("##"); |
||||
String funs = ""; |
||||
if (hasFunction) { |
||||
funs = rule.substring(rule.indexOf("##") + 2); |
||||
rule = rule.substring(0, rule.indexOf("##")); |
||||
} |
||||
JsonArray temp = rc.read(rule); |
||||
for (JsonElement element : temp) { |
||||
String str = element.toString(); |
||||
if (str.startsWith("\"")) str = str.substring(1, str.length() - 1); |
||||
if (hasFunction) str = evalFunction(funs, str); |
||||
if (StringHelper.isEmpty(str)) continue; |
||||
list.add(str); |
||||
if (isFirst) break; |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
/** |
||||
* @param rule ##!int:跳过前几个节点 |
||||
* @param rc |
||||
* @return |
||||
*/ |
||||
public List<ReadContext> getReadContextList(String rule, ReadContext rc) { |
||||
List<ReadContext> list = new ArrayList<>(); |
||||
int skip = 0; |
||||
if (rule.contains("##!")) { |
||||
try { |
||||
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3)); |
||||
} catch (Exception ignored) { |
||||
} |
||||
rule = rule.split("##")[0]; |
||||
} |
||||
if (StringHelper.isEmpty(rule)) return list; |
||||
JsonArray temp = rc.read(rule); |
||||
for (JsonElement element : temp) { |
||||
String str = element.toString(); |
||||
if (str.startsWith("\"")) str = str.substring(1, str.length() - 1); |
||||
list.add(getReadContext(str)); |
||||
} |
||||
return list.subList(skip, list.size()); |
||||
} |
||||
|
||||
public ReadContext getReadContext(Object obj) { |
||||
if (obj instanceof ReadContext) return (ReadContext) obj; |
||||
if (obj instanceof String) return parseContext.parse((String) obj); |
||||
return parseContext.parse(obj); |
||||
} |
||||
} |
@ -0,0 +1,117 @@ |
||||
package xyz.fycz.myreader.model.source; |
||||
|
||||
import org.seimicrawler.xpath.JXDocument; |
||||
import org.seimicrawler.xpath.JXNode; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/14 19:03 |
||||
*/ |
||||
public class XpathAnalyzer extends BaseAnalyzer { |
||||
|
||||
@Override |
||||
public List<String> getStringList(String rule, Object obj, boolean first) { |
||||
if (obj instanceof JXDocument) { |
||||
JXDocument jxDoc = (JXDocument) obj; |
||||
return getStringList(rule, jxDoc, first); |
||||
} else if (obj instanceof JXNode) { |
||||
JXNode jxNode = (JXNode) obj; |
||||
return getStringList(rule, jxNode, first); |
||||
} else { |
||||
return new ArrayList<>(); |
||||
} |
||||
} |
||||
|
||||
public List<String> getStringList(String rule, JXDocument jxDoc, boolean isFirst) { |
||||
List<String> list = new ArrayList<>(); |
||||
if (StringHelper.isEmpty(rule)) return list; |
||||
boolean hasFunction = rule.contains("##"); |
||||
String funs = ""; |
||||
if (hasFunction) { |
||||
funs = rule.substring(rule.indexOf("##") + 2); |
||||
rule = rule.substring(0, rule.indexOf("##")); |
||||
} |
||||
for (JXNode jxNode : jxDoc.selN(rule)) { |
||||
String str = jxNode.toString(); |
||||
if (hasFunction) str = evalFunction(funs, str); |
||||
if (StringHelper.isEmpty(str)) continue; |
||||
list.add(str); |
||||
if (isFirst) break; |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
public List<String> getStringList(String rule, JXNode jxNode, boolean isFirst) { |
||||
List<String> list = new ArrayList<>(); |
||||
if (StringHelper.isEmpty(rule)) return list; |
||||
boolean hasFunction = rule.contains("##"); |
||||
String funs = ""; |
||||
if (hasFunction) { |
||||
funs = rule.substring(rule.indexOf("##") + 2); |
||||
rule = rule.substring(0, rule.indexOf("##")); |
||||
} |
||||
for (JXNode jxNode1 : jxNode.sel(rule)) { |
||||
String str = jxNode1.toString(); |
||||
if (hasFunction) str = evalFunction(funs, str); |
||||
if (StringHelper.isEmpty(str)) continue; |
||||
list.add(str); |
||||
if (isFirst) break; |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
/** |
||||
* @param rule ##!int:跳过前几个节点 |
||||
* @param obj |
||||
* @return |
||||
*/ |
||||
public List<JXNode> getJXNodeList(String rule, Object obj) { |
||||
if (obj instanceof JXDocument) { |
||||
JXDocument jxDoc = (JXDocument) obj; |
||||
return getJXNodeList(rule, jxDoc); |
||||
} else if (obj instanceof JXNode) { |
||||
JXNode jxNode = (JXNode) obj; |
||||
return getJXNodeList(rule, jxNode); |
||||
} else { |
||||
return new ArrayList<>(); |
||||
} |
||||
} |
||||
|
||||
public List<JXNode> getJXNodeList(String rule, JXDocument JXDoc) { |
||||
List<JXNode> list = new ArrayList<>(); |
||||
int skip = 0; |
||||
if (rule.contains("##!")) { |
||||
try { |
||||
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3)); |
||||
} catch (Exception ignored) { |
||||
} |
||||
rule = rule.split("##")[0]; |
||||
} |
||||
if (StringHelper.isEmpty(rule)) return list; |
||||
List<JXNode> selN = JXDoc.selN(rule); |
||||
list = selN.subList(skip, selN.size()); |
||||
return list; |
||||
} |
||||
|
||||
public List<JXNode> getJXNodeList(String rule, JXNode jxNode) { |
||||
List<JXNode> list = new ArrayList<>(); |
||||
if (StringHelper.isEmpty(rule)) return list; |
||||
int skip = 0; |
||||
if (rule.contains("##!")) { |
||||
try { |
||||
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3)); |
||||
} catch (Exception ignored) { |
||||
} |
||||
rule = rule.split("##")[0]; |
||||
} |
||||
List<JXNode> selN = jxNode.sel(rule); |
||||
list = selN.subList(skip, selN.size()); |
||||
return list; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,145 @@ |
||||
package xyz.fycz.myreader.ui.activity; |
||||
|
||||
import android.app.Activity; |
||||
import android.text.TextUtils; |
||||
import android.util.TypedValue; |
||||
import android.view.Menu; |
||||
import android.view.MenuInflater; |
||||
import android.view.MenuItem; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.appcompat.widget.SearchView; |
||||
import androidx.appcompat.widget.Toolbar; |
||||
import androidx.fragment.app.Fragment; |
||||
|
||||
import com.google.android.material.tabs.TabLayout; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.App; |
||||
import xyz.fycz.myreader.base.BaseTabActivity; |
||||
import xyz.fycz.myreader.databinding.ActivityBooksourceBinding; |
||||
import xyz.fycz.myreader.ui.fragment.DIYSourceFragment; |
||||
import xyz.fycz.myreader.ui.fragment.LocalSourceFragment; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 9:14 |
||||
*/ |
||||
public class BookSourceActivity extends BaseTabActivity { |
||||
|
||||
private ActivityBooksourceBinding binding; |
||||
private SearchView searchView; |
||||
|
||||
@Override |
||||
protected void bindView() { |
||||
binding = ActivityBooksourceBinding.inflate(getLayoutInflater()); |
||||
setContentView(binding.getRoot()); |
||||
super.bindView(); |
||||
} |
||||
|
||||
@Override |
||||
protected List<Fragment> createTabFragments() { |
||||
return Arrays.asList(new LocalSourceFragment(this), |
||||
new DIYSourceFragment(this)); |
||||
} |
||||
|
||||
@Override |
||||
protected List<String> createTabTitles() { |
||||
return Arrays.asList("内置书源", "DIY书源"); |
||||
} |
||||
|
||||
@Override |
||||
protected void setUpToolbar(Toolbar toolbar) { |
||||
super.setUpToolbar(toolbar); |
||||
setStatusBarColor(R.color.colorPrimary, true); |
||||
} |
||||
|
||||
@Override |
||||
protected void initWidget() { |
||||
super.initWidget(); |
||||
binding.tabTlIndicator.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { |
||||
@Override |
||||
public void onTabSelected(TabLayout.Tab tab) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onTabUnselected(TabLayout.Tab tab) { |
||||
if (!TextUtils.isEmpty(searchView.getQuery())) { |
||||
App.getHandler().postDelayed(() ->{ |
||||
List<Fragment> fragments = getSupportFragmentManager().getFragments(); |
||||
switch (tab.getPosition()) { |
||||
case 0: |
||||
((LocalSourceFragment) fragments.get(0)).startSearch(""); |
||||
break; |
||||
case 1: |
||||
((DIYSourceFragment) fragments.get(1)).startSearch(""); |
||||
break; |
||||
} |
||||
}, 300); |
||||
} |
||||
searchView.onActionViewCollapsed(); |
||||
} |
||||
|
||||
@Override |
||||
public void onTabReselected(TabLayout.Tab tab) { |
||||
|
||||
} |
||||
}); |
||||
} |
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
MenuInflater inflater = getMenuInflater(); |
||||
inflater.inflate(R.menu.menu_view_search, menu); |
||||
MenuItem search = menu.findItem(R.id.action_search); |
||||
searchView = (SearchView) search.getActionView(); |
||||
TextView textView = searchView.findViewById(R.id.search_src_text); |
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); |
||||
searchView.setQueryHint("搜索书源"); |
||||
searchView.setMaxWidth(getResources().getDisplayMetrics().widthPixels); |
||||
searchView.onActionViewCollapsed(); |
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
||||
@Override |
||||
public boolean onQueryTextSubmit(String query) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onQueryTextChange(String newText) { |
||||
List<Fragment> fragments = getSupportFragmentManager().getFragments(); |
||||
switch (binding.tabVp.getCurrentItem()) { |
||||
case 0: |
||||
((LocalSourceFragment) fragments.get(0)).startSearch(newText); |
||||
break; |
||||
case 1: |
||||
((DIYSourceFragment) fragments.get(1)).startSearch(newText); |
||||
break; |
||||
} |
||||
return false; |
||||
} |
||||
}); |
||||
return super.onCreateOptionsMenu(menu); |
||||
} |
||||
|
||||
@Override |
||||
public void finish() { |
||||
setResult(Activity.RESULT_OK); |
||||
super.finish(); |
||||
} |
||||
|
||||
@Override |
||||
public void onBackPressed() { |
||||
if (!"".contentEquals(searchView.getQuery())) { |
||||
searchView.onActionViewCollapsed(); |
||||
} else { |
||||
super.onBackPressed(); |
||||
} |
||||
} |
||||
|
||||
public SearchView getSearchView() { |
||||
return searchView; |
||||
} |
||||
} |
@ -0,0 +1,312 @@ |
||||
package xyz.fycz.myreader.ui.activity; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.util.TypedValue; |
||||
import android.view.Menu; |
||||
import android.view.MenuItem; |
||||
import android.view.MotionEvent; |
||||
import android.view.View; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
import androidx.appcompat.widget.SearchView; |
||||
import androidx.appcompat.widget.Toolbar; |
||||
import androidx.recyclerview.widget.ItemTouchHelper; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.ObservableOnSubscribe; |
||||
import io.reactivex.android.schedulers.AndroidSchedulers; |
||||
import io.reactivex.annotations.NonNull; |
||||
import io.reactivex.schedulers.Schedulers; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BaseActivity; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.base.observer.MySingleObserver; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.databinding.ActivityReplaceRuleBinding; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.model.ReplaceRuleManager; |
||||
import xyz.fycz.myreader.ui.adapter.ReplaceRuleAdapter; |
||||
import xyz.fycz.myreader.ui.adapter.helper.ItemTouchCallback; |
||||
import xyz.fycz.myreader.ui.dialog.DialogCreator; |
||||
import xyz.fycz.myreader.ui.dialog.MyAlertDialog; |
||||
import xyz.fycz.myreader.ui.dialog.ReplaceDialog; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.ClipBoardUtil; |
||||
import xyz.fycz.myreader.util.utils.FileUtils; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
import xyz.fycz.myreader.widget.DividerItemDecoration; |
||||
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout; |
||||
|
||||
import static android.text.TextUtils.isEmpty; |
||||
import static xyz.fycz.myreader.util.UriFileUtil.getPath; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/1/19 10:02 |
||||
*/ |
||||
public class ReplaceRuleActivity extends BaseActivity { |
||||
|
||||
private ActivityReplaceRuleBinding binding; |
||||
|
||||
private SearchView searchView; |
||||
private List<ReplaceRuleBean> mReplaceRules; |
||||
private ReplaceRuleAdapter mAdapter; |
||||
|
||||
@Override |
||||
protected void bindView() { |
||||
binding = ActivityReplaceRuleBinding.inflate(getLayoutInflater()); |
||||
setContentView(binding.getRoot()); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
ReplaceRuleManager.getAll().subscribe(new MySingleObserver<List<ReplaceRuleBean>>() { |
||||
@Override |
||||
public void onSuccess(@NonNull List<ReplaceRuleBean> replaceRuleBeans) { |
||||
mReplaceRules = replaceRuleBeans; |
||||
initRuleList(); |
||||
setUpBarTitle(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
protected void setUpToolbar(Toolbar toolbar) { |
||||
super.setUpToolbar(toolbar); |
||||
setUpBarTitle(); |
||||
setStatusBarColor(R.color.colorPrimary, true); |
||||
} |
||||
|
||||
private void setUpBarTitle() { |
||||
getSupportActionBar().setTitle(String.format("%s(共%s个)", |
||||
getString(R.string.replace_rule), mReplaceRules == null ? 0 : mReplaceRules.size())); |
||||
} |
||||
|
||||
protected void initRuleList() { |
||||
mAdapter = new ReplaceRuleAdapter(this, mReplaceRules, new ReplaceRuleAdapter.OnSwipeListener() { |
||||
@Override |
||||
public void onDel(int which, ReplaceRuleBean bean) { |
||||
mReplaceRules.remove(bean); |
||||
mAdapter.removeItem(which); |
||||
setUpBarTitle(); |
||||
} |
||||
|
||||
@Override |
||||
public void onTop(int which, ReplaceRuleBean bean) { |
||||
if (which > 0 && which < mReplaceRules.size()) { |
||||
mReplaceRules.remove(bean); |
||||
mReplaceRules.add(0, bean); |
||||
mAdapter.toTop(which, bean); |
||||
} |
||||
} |
||||
}); |
||||
binding.rvRuleList.setLayoutManager(new LinearLayoutManager(this)); |
||||
binding.rvRuleList.setAdapter(mAdapter); |
||||
//设置分割线
|
||||
binding.rvRuleList.addItemDecoration(new DividerItemDecoration(this)); |
||||
//添加数据
|
||||
mAdapter.refreshItems(mReplaceRules); |
||||
} |
||||
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility") |
||||
@Override |
||||
protected void initClick() { |
||||
super.initClick(); |
||||
binding.rvRuleList.setOnTouchListener((v, event) -> { |
||||
if (event.getAction() == MotionEvent.ACTION_UP) { |
||||
SwipeMenuLayout viewCache = SwipeMenuLayout.getViewCache(); |
||||
if (null != viewCache) { |
||||
viewCache.smoothClose(); |
||||
} |
||||
} |
||||
return false; |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
getMenuInflater().inflate(R.menu.menu_rule, menu); |
||||
|
||||
MenuItem search = menu.findItem(R.id.action_search); |
||||
searchView = (SearchView) search.getActionView(); |
||||
TextView textView = (TextView) searchView.findViewById(R.id.search_src_text); |
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); |
||||
searchView.setQueryHint("搜索替换规则"); |
||||
searchView.setMaxWidth(getResources().getDisplayMetrics().widthPixels); |
||||
searchView.onActionViewCollapsed(); |
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
||||
@Override |
||||
public boolean onQueryTextSubmit(String query) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onQueryTextChange(String newText) { |
||||
mAdapter.getFilter().filter(newText); |
||||
return false; |
||||
} |
||||
}); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onOptionsItemSelected(MenuItem item) { |
||||
int itemId = item.getItemId(); |
||||
if (itemId == R.id.action_add_rule) { |
||||
ReplaceRuleBean newRuleBean = new ReplaceRuleBean(); |
||||
newRuleBean.setReplaceSummary(""); |
||||
newRuleBean.setEnable(true); |
||||
newRuleBean.setRegex(""); |
||||
newRuleBean.setIsRegex(false); |
||||
newRuleBean.setReplacement(""); |
||||
newRuleBean.setSerialNumber(0); |
||||
newRuleBean.setUseTo(""); |
||||
ReplaceDialog replaceDialog = new ReplaceDialog(this, newRuleBean |
||||
, () -> { |
||||
ToastUtils.showSuccess("内容替换规则添加成功!"); |
||||
mReplaceRules.add(newRuleBean); |
||||
mAdapter.addItem(newRuleBean); |
||||
setUpBarTitle(); |
||||
refreshUI(); |
||||
}); |
||||
replaceDialog.show(getSupportFragmentManager(), "replaceRule"); |
||||
} else if (itemId == R.id.action_import) { |
||||
MyAlertDialog.build(this) |
||||
.setTitle("导入规则") |
||||
.setItems(R.array.import_rule, (dialog, which) -> { |
||||
if (which == 0) { |
||||
String text = ClipBoardUtil.paste(this); |
||||
if (!isEmpty(text)) { |
||||
importDataS(text); |
||||
} else { |
||||
ToastUtils.showError("剪切板内容为空,导入失败"); |
||||
} |
||||
} else if (which == 1) { |
||||
ToastUtils.showInfo("请选择内容替换规则JSON文件"); |
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
||||
intent.addCategory(Intent.CATEGORY_OPENABLE); |
||||
intent.setType("application/json"); |
||||
startActivityForResult(intent, APPCONST.REQUEST_IMPORT_REPLACE_RULE); |
||||
} else { |
||||
String[] url = new String[1]; |
||||
MyAlertDialog.createInputDia(this, "网络导入", |
||||
"请输入网址", "", true, 200, |
||||
text -> url[0] = text, |
||||
(dialog1, which1) -> importDataS(url[0])); |
||||
|
||||
} |
||||
}).show(); |
||||
} else if (itemId == R.id.action_export) { |
||||
if (mReplaceRules == null || mReplaceRules.size() == 0) { |
||||
ToastUtils.showWarring("当前没有任何规则,无法导出!"); |
||||
return true; |
||||
} |
||||
if (FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(mReplaceRules), |
||||
FileUtils.getFile(APPCONST.FILE_DIR + "ReplaceRule.json"))) { |
||||
DialogCreator.createTipDialog(this, |
||||
"内容替换规则导出成功,导出位置:" + APPCONST.FILE_DIR + "ReplaceRule.json"); |
||||
} |
||||
} else if (itemId == R.id.action_reverse) { |
||||
for (ReplaceRuleBean ruleBean : mReplaceRules) { |
||||
ruleBean.setEnable(!ruleBean.getEnable()); |
||||
} |
||||
ReplaceRuleManager.addDataS(mReplaceRules); |
||||
mAdapter.notifyDataSetChanged(); |
||||
refreshUI(); |
||||
} else if (itemId == R.id.action_delete) { |
||||
DialogCreator.createCommonDialog(this, "删除禁用规则", |
||||
"确定要删除所有禁用规则吗?", true, |
||||
(dialog, which) -> { |
||||
List<ReplaceRuleBean> ruleBeans = new ArrayList<>(); |
||||
for (ReplaceRuleBean ruleBean : mReplaceRules) { |
||||
if (!ruleBean.getEnable()) { |
||||
ruleBeans.add(ruleBean); |
||||
} |
||||
} |
||||
ReplaceRuleManager.delDataS(ruleBeans); |
||||
mReplaceRules.removeAll(ruleBeans); |
||||
mAdapter.removeItems(ruleBeans); |
||||
ToastUtils.showSuccess("禁用规则删除成功"); |
||||
setUpBarTitle(); |
||||
refreshUI(); |
||||
}, null); |
||||
} |
||||
return super.onOptionsItemSelected(item); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { |
||||
if (resultCode == RESULT_OK) { |
||||
if (requestCode == APPCONST.REQUEST_IMPORT_REPLACE_RULE) { |
||||
String path = getPath(this, data.getData()); |
||||
String json = FileUtils.readText(path); |
||||
if (!isEmpty(json)) { |
||||
importDataS(json); |
||||
} else { |
||||
ToastUtils.showError("文件读取失败"); |
||||
} |
||||
} |
||||
} |
||||
super.onActivityResult(requestCode, resultCode, data); |
||||
} |
||||
|
||||
public void importDataS(String text) { |
||||
Observable<Boolean> observable = ReplaceRuleManager.importReplaceRule(text); |
||||
if (observable != null) { |
||||
observable.subscribe(new MyObserver<Boolean>() { |
||||
@Override |
||||
public void onNext(Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
mReplaceRules = ReplaceRuleManager.getAllRules(); |
||||
mAdapter.setBeans(mReplaceRules); |
||||
mAdapter.refreshItems(mReplaceRules); |
||||
setUpBarTitle(); |
||||
refreshUI(); |
||||
ToastUtils.showSuccess("内容替换规则导入成功"); |
||||
} else { |
||||
ToastUtils.showError("格式不对"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("格式不对"); |
||||
} |
||||
}); |
||||
} else { |
||||
ToastUtils.showError("导入失败"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onBackPressed() { |
||||
if (!"".contentEquals(searchView.getQuery())){ |
||||
searchView.onActionViewCollapsed(); |
||||
}else { |
||||
super.onBackPressed(); |
||||
} |
||||
} |
||||
|
||||
private void refreshUI() { |
||||
Intent result = new Intent(); |
||||
result.putExtra(APPCONST.RESULT_NEED_REFRESH, true); |
||||
setResult(AppCompatActivity.RESULT_OK, result); |
||||
} |
||||
} |
@ -1,258 +0,0 @@ |
||||
package xyz.fycz.myreader.ui.activity; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.view.Menu; |
||||
import android.view.MenuItem; |
||||
import android.view.MotionEvent; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
import androidx.appcompat.widget.Toolbar; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.annotations.NonNull; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BaseActivity; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.base.observer.MySingleObserver; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.databinding.ActivityRuleBinding; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.model.ReplaceRuleManager; |
||||
import xyz.fycz.myreader.ui.adapter.ReplaceRuleAdapter; |
||||
import xyz.fycz.myreader.ui.dialog.DialogCreator; |
||||
import xyz.fycz.myreader.ui.dialog.MyAlertDialog; |
||||
import xyz.fycz.myreader.ui.dialog.ReplaceDialog; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.ClipBoardUtil; |
||||
import xyz.fycz.myreader.util.utils.FileUtils; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
import xyz.fycz.myreader.widget.DividerItemDecoration; |
||||
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout; |
||||
|
||||
import static android.text.TextUtils.isEmpty; |
||||
import static xyz.fycz.myreader.util.UriFileUtil.getPath; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/1/19 10:02 |
||||
*/ |
||||
public class RuleActivity extends BaseActivity { |
||||
|
||||
private ActivityRuleBinding binding; |
||||
|
||||
private List<ReplaceRuleBean> mReplaceRules; |
||||
private ReplaceRuleAdapter mAdapter; |
||||
|
||||
@Override |
||||
protected void bindView() { |
||||
binding = ActivityRuleBinding.inflate(getLayoutInflater()); |
||||
setContentView(binding.getRoot()); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
ReplaceRuleManager.getAll().subscribe(new MySingleObserver<List<ReplaceRuleBean>>() { |
||||
@Override |
||||
public void onSuccess(@NonNull List<ReplaceRuleBean> replaceRuleBeans) { |
||||
mReplaceRules = replaceRuleBeans; |
||||
initRuleList(); |
||||
setUpBarTitle(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
protected void setUpToolbar(Toolbar toolbar) { |
||||
super.setUpToolbar(toolbar); |
||||
setUpBarTitle(); |
||||
setStatusBarColor(R.color.colorPrimary, true); |
||||
} |
||||
|
||||
private void setUpBarTitle() { |
||||
getSupportActionBar().setTitle(String.format("%s(共%s个)", |
||||
getString(R.string.replace_rule), mReplaceRules == null ? 0 : mReplaceRules.size())); |
||||
} |
||||
|
||||
protected void initRuleList() { |
||||
mAdapter = new ReplaceRuleAdapter(this, which -> { |
||||
mReplaceRules.remove(which); |
||||
mAdapter.removeItem(which); |
||||
mAdapter.notifyItemRemoved(which); |
||||
if(which != mReplaceRules.size()) |
||||
mAdapter.notifyItemRangeChanged(which, mReplaceRules.size() - which); |
||||
setUpBarTitle(); |
||||
}); |
||||
binding.rvRuleList.setLayoutManager(new LinearLayoutManager(this)); |
||||
binding.rvRuleList.setAdapter(mAdapter); |
||||
//设置分割线
|
||||
binding.rvRuleList.addItemDecoration(new DividerItemDecoration(this)); |
||||
mAdapter.refreshItems(mReplaceRules); |
||||
} |
||||
|
||||
@SuppressLint("ClickableViewAccessibility") |
||||
@Override |
||||
protected void initClick() { |
||||
super.initClick(); |
||||
binding.rvRuleList.setOnTouchListener((v, event) -> { |
||||
if (event.getAction() == MotionEvent.ACTION_UP) { |
||||
SwipeMenuLayout viewCache = SwipeMenuLayout.getViewCache(); |
||||
if (null != viewCache) { |
||||
viewCache.smoothClose(); |
||||
} |
||||
} |
||||
return false; |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
getMenuInflater().inflate(R.menu.menu_rule, menu); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onOptionsItemSelected(MenuItem item) { |
||||
switch (item.getItemId()) { |
||||
case R.id.action_add_rule: |
||||
ReplaceRuleBean newRuleBean = new ReplaceRuleBean(); |
||||
newRuleBean.setReplaceSummary(""); |
||||
newRuleBean.setEnable(true); |
||||
newRuleBean.setRegex(""); |
||||
newRuleBean.setIsRegex(false); |
||||
newRuleBean.setReplacement(""); |
||||
newRuleBean.setSerialNumber(0); |
||||
newRuleBean.setUseTo(""); |
||||
ReplaceDialog replaceDialog = new ReplaceDialog(this, newRuleBean |
||||
, () -> { |
||||
ToastUtils.showSuccess("内容替换规则添加成功!"); |
||||
mReplaceRules.add(newRuleBean); |
||||
mAdapter.addItem(newRuleBean); |
||||
setUpBarTitle(); |
||||
refreshUI(); |
||||
}); |
||||
replaceDialog.show(getSupportFragmentManager(), "replaceRule"); |
||||
break; |
||||
case R.id.action_import: |
||||
MyAlertDialog.build(this) |
||||
.setTitle("导入规则") |
||||
.setItems(R.array.import_rule, (dialog, which) -> { |
||||
if (which == 0) { |
||||
String text = ClipBoardUtil.paste(this); |
||||
if (!isEmpty(text)) { |
||||
importDataS(text); |
||||
} else { |
||||
ToastUtils.showError("剪切板内容为空,导入失败"); |
||||
} |
||||
} else { |
||||
ToastUtils.showInfo("请选择内容替换规则JSON文件"); |
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
||||
intent.addCategory(Intent.CATEGORY_OPENABLE); |
||||
intent.setType("application/json"); |
||||
startActivityForResult(intent, APPCONST.REQUEST_IMPORT_REPLACE_RULE); |
||||
} |
||||
}).show(); |
||||
break; |
||||
case R.id.action_export: |
||||
if (mReplaceRules == null || mReplaceRules.size() == 0){ |
||||
ToastUtils.showWarring("当前没有任何规则,无法导出!"); |
||||
return true; |
||||
} |
||||
if (FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(mReplaceRules), |
||||
FileUtils.getFile(APPCONST.FILE_DIR + "ReplaceRule.json"))) { |
||||
DialogCreator.createTipDialog(this, |
||||
"内容替换规则导出成功,导出位置:" + APPCONST.FILE_DIR + "ReplaceRule.json"); |
||||
} |
||||
break; |
||||
case R.id.action_reverse: |
||||
for (ReplaceRuleBean ruleBean : mReplaceRules) { |
||||
ruleBean.setEnable(!ruleBean.getEnable()); |
||||
} |
||||
ReplaceRuleManager.addDataS(mReplaceRules); |
||||
mAdapter.notifyDataSetChanged(); |
||||
refreshUI(); |
||||
break; |
||||
case R.id.action_delete: |
||||
DialogCreator.createCommonDialog(this, "删除禁用规则", |
||||
"确定要删除所有禁用规则吗?", true, |
||||
(dialog, which) -> { |
||||
List<ReplaceRuleBean> ruleBeans = new ArrayList<>(); |
||||
for (ReplaceRuleBean ruleBean : mReplaceRules) { |
||||
if (!ruleBean.getEnable()) { |
||||
ruleBeans.add(ruleBean); |
||||
} |
||||
} |
||||
ReplaceRuleManager.delDataS(ruleBeans); |
||||
mReplaceRules.removeAll(ruleBeans); |
||||
mAdapter.removeItems(ruleBeans); |
||||
ToastUtils.showSuccess("禁用规则删除成功"); |
||||
setUpBarTitle(); |
||||
refreshUI(); |
||||
}, null); |
||||
break; |
||||
} |
||||
return super.onOptionsItemSelected(item); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { |
||||
if (resultCode == RESULT_OK) { |
||||
if (requestCode == APPCONST.REQUEST_IMPORT_REPLACE_RULE) { |
||||
String path = getPath(this, data.getData()); |
||||
String json = FileUtils.readText(path); |
||||
if (!isEmpty(json)) { |
||||
importDataS(json); |
||||
} else { |
||||
ToastUtils.showError("文件读取失败"); |
||||
} |
||||
} |
||||
} |
||||
super.onActivityResult(requestCode, resultCode, data); |
||||
} |
||||
|
||||
public void importDataS(String text) { |
||||
Observable<Boolean> observable = ReplaceRuleManager.importReplaceRule(text); |
||||
if (observable != null) { |
||||
observable.subscribe(new MyObserver<Boolean>() { |
||||
@Override |
||||
public void onNext(Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
mReplaceRules = ReplaceRuleManager.getAllRules(); |
||||
mAdapter.refreshItems(mReplaceRules); |
||||
setUpBarTitle(); |
||||
refreshUI(); |
||||
ToastUtils.showSuccess("内容替换规则导入成功"); |
||||
} else { |
||||
ToastUtils.showError("格式不对"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("格式不对"); |
||||
} |
||||
}); |
||||
} else { |
||||
ToastUtils.showError("导入失败"); |
||||
} |
||||
} |
||||
|
||||
private void refreshUI() { |
||||
Intent result = new Intent(); |
||||
result.putExtra(APPCONST.RESULT_NEED_REFRESH, true); |
||||
setResult(AppCompatActivity.RESULT_OK, result); |
||||
} |
||||
} |
@ -0,0 +1,273 @@ |
||||
package xyz.fycz.myreader.ui.activity; |
||||
|
||||
import android.os.Bundle; |
||||
import android.view.Menu; |
||||
import android.view.MenuInflater; |
||||
import android.view.MenuItem; |
||||
import android.view.View; |
||||
|
||||
import androidx.appcompat.widget.Toolbar; |
||||
|
||||
import com.google.android.material.tabs.TabLayout; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.ObservableOnSubscribe; |
||||
import io.reactivex.annotations.NonNull; |
||||
import io.reactivex.disposables.Disposable; |
||||
import okhttp3.MediaType; |
||||
import okhttp3.RequestBody; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BaseActivity; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.databinding.ActivitySourceDebugBinding; |
||||
import xyz.fycz.myreader.entity.sourcedebug.DebugBook; |
||||
import xyz.fycz.myreader.entity.sourcedebug.DebugChapter; |
||||
import xyz.fycz.myreader.entity.sourcedebug.DebugEntity; |
||||
import xyz.fycz.myreader.entity.sourcedebug.ListResult; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.greendao.entity.Chapter; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
import xyz.fycz.myreader.util.utils.NetworkUtils; |
||||
import xyz.fycz.myreader.util.utils.OkHttpUtils; |
||||
import xyz.fycz.myreader.util.utils.ProgressUtils; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; |
||||
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; |
||||
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; |
||||
import xyz.fycz.myreader.widget.codeview.Language; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/12 18:55 |
||||
*/ |
||||
public class SourceDebugActivity extends BaseActivity { |
||||
private ActivitySourceDebugBinding binding; |
||||
|
||||
private DebugEntity debugEntity; |
||||
private ReadCrawler rc; |
||||
private Disposable disposable; |
||||
|
||||
@Override |
||||
protected void bindView() { |
||||
binding = ActivitySourceDebugBinding.inflate(getLayoutInflater()); |
||||
setContentView(binding.getRoot()); |
||||
} |
||||
|
||||
@Override |
||||
protected void setUpToolbar(Toolbar toolbar) { |
||||
super.setUpToolbar(toolbar); |
||||
setStatusBarColor(R.color.colorPrimary, true); |
||||
switch (debugEntity.getDebugMode()) { |
||||
case DebugEntity.SEARCH: |
||||
default: |
||||
getSupportActionBar().setTitle(R.string.debug_search); |
||||
break; |
||||
case DebugEntity.INFO: |
||||
getSupportActionBar().setTitle(R.string.debug_info); |
||||
break; |
||||
case DebugEntity.TOC: |
||||
getSupportActionBar().setTitle(R.string.debug_toc); |
||||
break; |
||||
case DebugEntity.CONTENT: |
||||
getSupportActionBar().setTitle(R.string.debug_content); |
||||
break; |
||||
} |
||||
initDebugEntity(); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
debugEntity = getIntent().getParcelableExtra("debugEntity"); |
||||
rc = ReadCrawlerUtil.getReadCrawler(debugEntity.getBookSource()); |
||||
} |
||||
|
||||
@Override |
||||
protected void initWidget() { |
||||
super.initWidget(); |
||||
binding.tvTip.setText(getString(R.string.debug_desc, |
||||
debugEntity.getBookSource().getSourceName(), |
||||
debugEntity.getBookSource().getSourceUrl(), |
||||
debugEntity.getUrl())); |
||||
binding.rvParseResult.setLanguage(Language.JSON); |
||||
binding.rvSourceCode.setLanguage(Language.HTML); |
||||
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { |
||||
@Override |
||||
public void onTabSelected(TabLayout.Tab tab) { |
||||
if (tab.getPosition() == 0) { |
||||
binding.rvSourceCode.setVisibility(View.GONE); |
||||
binding.rvParseResult.setVisibility(View.VISIBLE); |
||||
} else if (tab.getPosition() == 1) { |
||||
binding.rvSourceCode.setVisibility(View.VISIBLE); |
||||
binding.rvParseResult.setVisibility(View.GONE); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onTabUnselected(TabLayout.Tab tab) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onTabReselected(TabLayout.Tab tab) { |
||||
|
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void onBackPressed() { |
||||
if (ProgressUtils.isShowing()) { |
||||
ProgressUtils.dismiss(); |
||||
} else { |
||||
super.onBackPressed(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onDestroy() { |
||||
ProgressUtils.dismiss(); |
||||
super.onDestroy(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
MenuInflater inflater = getMenuInflater(); |
||||
inflater.inflate(R.menu.menu_debug, menu); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onOptionsItemSelected(MenuItem item) { |
||||
if (item.getItemId() == R.id.action_re_request) { |
||||
initDebugEntity(); |
||||
} |
||||
return super.onOptionsItemSelected(item); |
||||
} |
||||
|
||||
private void initDebugEntity() { |
||||
ProgressUtils.show(this, "正在请求...", "取消", (dialog, which) -> { |
||||
if (disposable != null) { |
||||
disposable.dispose(); |
||||
} |
||||
}); |
||||
Observable.create((ObservableOnSubscribe<Boolean>) emitter -> { |
||||
try { |
||||
String url = debugEntity.getUrl(); |
||||
if (debugEntity.getDebugMode() == DebugEntity.SEARCH && rc.isPost()) { |
||||
String[] urlInfo = url.split(","); |
||||
url = NetworkUtils.getAbsoluteURL(rc.getNameSpace(), urlInfo[0]); |
||||
String body = urlInfo[1]; |
||||
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded"); |
||||
RequestBody requestBody = RequestBody.create(mediaType, body); |
||||
debugEntity.setHtml(OkHttpUtils.getHtml(url, requestBody, rc.getCharset())); |
||||
} else { |
||||
debugEntity.setHtml(OkHttpUtils.getHtml(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), url), rc.getCharset())); |
||||
} |
||||
ListResult listResult = new ListResult(); |
||||
switch (debugEntity.getDebugMode()) { |
||||
case DebugEntity.SEARCH: |
||||
default: |
||||
List<DebugBook> debugBooks = book2DebugBook(rc.getBooksFromSearchHtml(debugEntity.getHtml()).values()); |
||||
listResult.set信息(String.format("解析完毕,共%s本书籍", debugBooks.size())); |
||||
listResult.set结果(debugBooks); |
||||
debugEntity.setParseResult(GsonExtensionsKt.getGSON() |
||||
.toJson(listResult)); |
||||
break; |
||||
case DebugEntity.INFO: |
||||
debugEntity.setParseResult(GsonExtensionsKt.getGSON() |
||||
.toJson(book2DebugBook(((BookInfoCrawler) rc).getBookInfo(debugEntity.getHtml(), new Book())))); |
||||
break; |
||||
case DebugEntity.TOC: |
||||
List<DebugChapter> debugChapters = chapter2DebugChapter(rc.getChaptersFromHtml(debugEntity.getHtml())); |
||||
listResult.set信息(String.format("解析完毕,共%s个章节", debugChapters.size())); |
||||
listResult.set结果(debugChapters); |
||||
debugEntity.setParseResult(GsonExtensionsKt.getGSON() |
||||
.toJson(listResult)); |
||||
break; |
||||
case DebugEntity.CONTENT: |
||||
debugEntity.setParseResult(rc.getContentFormHtml(debugEntity.getHtml())); |
||||
break; |
||||
} |
||||
emitter.onNext(true); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
emitter.onError(e); |
||||
} |
||||
emitter.onComplete(); |
||||
}).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver<Boolean>() { |
||||
|
||||
@Override |
||||
public void onSubscribe(Disposable d) { |
||||
disposable = d; |
||||
} |
||||
|
||||
@Override |
||||
public void onNext(@NonNull Boolean flag) { |
||||
binding.rvParseResult.setCode(debugEntity.getParseResult()).apply(); |
||||
binding.rvSourceCode.setCode(debugEntity.getHtml()).apply(); |
||||
ProgressUtils.dismiss(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
binding.rvParseResult.setCode(String.format("{\n\b\b\b\b\"result\": \"error\", \n\b\b\b\b\"msg\": \"%s\"\n}" |
||||
, e.getLocalizedMessage().replace("\"", "\\\""))).apply(); |
||||
binding.rvSourceCode.setCode(debugEntity.getHtml()).apply(); |
||||
ProgressUtils.dismiss(); |
||||
} |
||||
|
||||
}); |
||||
} |
||||
|
||||
private List<DebugBook> book2DebugBook(List<Book> books) { |
||||
List<DebugBook> debugBooks = new ArrayList<>(); |
||||
for (Book book : books) { |
||||
debugBooks.add(book2DebugBook(book)); |
||||
} |
||||
return debugBooks; |
||||
} |
||||
|
||||
private DebugBook book2DebugBook(Book book) { |
||||
DebugBook debugBook = new DebugBook(); |
||||
/*debugBook.setName(book.getName()); |
||||
debugBook.setAuthor(book.getAuthor()); |
||||
debugBook.setType(book.getType()); |
||||
debugBook.setDesc(book.getDesc()); |
||||
debugBook.setWordCount(book.getWordCount()); |
||||
debugBook.setStatus(book.getStatus()); |
||||
debugBook.setLastChapter(book.getNewestChapterTitle()); |
||||
debugBook.setUpdateTime(book.getUpdateDate()); |
||||
debugBook.setImgUrl(book.getImgUrl()); |
||||
debugBook.setTocUrl(book.getChapterUrl()); |
||||
debugBook.setInfoUrl(book.getInfoUrl());*/ |
||||
debugBook.set书名(book.getName()); |
||||
debugBook.set作者(book.getAuthor()); |
||||
debugBook.set分类(book.getType()); |
||||
debugBook.set简介(book.getDesc()); |
||||
debugBook.set字数(book.getWordCount()); |
||||
debugBook.set连载状态(book.getStatus()); |
||||
debugBook.set最新章节(book.getNewestChapterTitle()); |
||||
debugBook.set更新时间(book.getUpdateDate()); |
||||
debugBook.set封面链接(book.getImgUrl()); |
||||
debugBook.set目录链接(book.getChapterUrl()); |
||||
debugBook.set详情链接(book.getInfoUrl()); |
||||
return debugBook; |
||||
} |
||||
|
||||
private List<DebugChapter> chapter2DebugChapter(List<Chapter> chapters) { |
||||
List<DebugChapter> debugChapters = new ArrayList<>(); |
||||
for (Chapter chapter : chapters) { |
||||
DebugChapter debugChapter = new DebugChapter(); |
||||
/*debugChapter.setTitle(chapter.getTitle()); |
||||
debugChapter.setUrl(chapter.getUrl());*/ |
||||
debugChapter.set章节名称(chapter.getTitle()); |
||||
debugChapter.set章节链接(chapter.getUrl()); |
||||
debugChapters.add(debugChapter); |
||||
} |
||||
return debugChapters; |
||||
} |
||||
} |
@ -0,0 +1,280 @@ |
||||
package xyz.fycz.myreader.ui.activity; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.view.Menu; |
||||
import android.view.MenuItem; |
||||
import android.view.View; |
||||
import android.widget.AdapterView; |
||||
import android.widget.ArrayAdapter; |
||||
|
||||
import androidx.appcompat.widget.Toolbar; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
||||
import com.google.android.material.tabs.TabLayout; |
||||
|
||||
import java.net.URLEncoder; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.SysManager; |
||||
import xyz.fycz.myreader.base.BaseActivity; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.databinding.ActivitySourceEditBinding; |
||||
import xyz.fycz.myreader.entity.sourcedebug.DebugEntity; |
||||
import xyz.fycz.myreader.entity.sourceedit.EditEntity; |
||||
import xyz.fycz.myreader.entity.sourceedit.EditEntityUtil; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.greendao.entity.rule.SearchRule; |
||||
import xyz.fycz.myreader.model.source.BookSourceManager; |
||||
import xyz.fycz.myreader.ui.adapter.SourceEditAdapter; |
||||
import xyz.fycz.myreader.ui.dialog.DialogCreator; |
||||
import xyz.fycz.myreader.ui.dialog.MyAlertDialog; |
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.webapi.crawler.source.MatcherCrawler; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/9 10:54 |
||||
*/ |
||||
public class SourceEditActivity extends BaseActivity { |
||||
private ActivitySourceEditBinding binding; |
||||
|
||||
private BookSource source; |
||||
private List<EditEntity> sourceEntities; |
||||
private List<EditEntity> searchEntities; |
||||
private List<EditEntity> infoEntities; |
||||
private List<EditEntity> tocEntities; |
||||
private List<EditEntity> contentEntities; |
||||
private EditEntityUtil entityUtil; |
||||
|
||||
private SourceEditAdapter adapter; |
||||
|
||||
@Override |
||||
protected void bindView() { |
||||
binding = ActivitySourceEditBinding.inflate(getLayoutInflater()); |
||||
setContentView(binding.getRoot()); |
||||
} |
||||
|
||||
@Override |
||||
protected void setUpToolbar(Toolbar toolbar) { |
||||
super.setUpToolbar(toolbar); |
||||
setStatusBarColor(R.color.colorPrimary, true); |
||||
getSupportActionBar().setTitle("书源编辑"); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
source = getIntent().getParcelableExtra(APPCONST.BOOK_SOURCE); |
||||
if (source == null) { |
||||
source = new BookSource(); |
||||
source.setEnable(true); |
||||
source.setSourceType(APPCONST.XPATH); |
||||
} |
||||
entityUtil = EditEntityUtil.INSTANCE; |
||||
adapter = new SourceEditAdapter(); |
||||
} |
||||
|
||||
private void initEntities(BookSource source) { |
||||
sourceEntities = entityUtil.getSourceEntities(source); |
||||
searchEntities = entityUtil.getSearchEntities(source.getSearchRule()); |
||||
infoEntities = entityUtil.getInfoEntities(source.getInfoRule()); |
||||
tocEntities = entityUtil.getTocEntities(source.getTocRule()); |
||||
contentEntities = entityUtil.getContentEntities(source.getContentRule()); |
||||
} |
||||
|
||||
@Override |
||||
protected void initWidget() { |
||||
super.initWidget(); |
||||
binding.cbSourceEnable.setChecked(source.getEnable()); |
||||
initSpinner(); |
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); |
||||
binding.recyclerView.setAdapter(adapter); |
||||
upRecyclerView(); |
||||
} |
||||
|
||||
private void initSpinner() { |
||||
ArrayAdapter<CharSequence> sourceTypeAdapter = ArrayAdapter.createFromResource(this, |
||||
R.array.source_type, android.R.layout.simple_spinner_item); |
||||
sourceTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
||||
binding.sSourceType.setAdapter(sourceTypeAdapter); |
||||
int sourceType = 0; |
||||
if (source.getSourceType() == null) source.setSourceType(APPCONST.MATCHER); |
||||
switch (source.getSourceType()) { |
||||
case APPCONST.MATCHER: |
||||
sourceType = 0; |
||||
break; |
||||
case APPCONST.XPATH: |
||||
sourceType = 1; |
||||
break; |
||||
case APPCONST.JSON_PATH: |
||||
sourceType = 2; |
||||
break; |
||||
} |
||||
binding.sSourceType.setSelection(sourceType); |
||||
} |
||||
|
||||
@Override |
||||
protected void initClick() { |
||||
super.initClick(); |
||||
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { |
||||
@Override |
||||
public void onTabSelected(TabLayout.Tab tab) { |
||||
setEditEntities(tab.getPosition()); |
||||
} |
||||
|
||||
@Override |
||||
public void onTabUnselected(TabLayout.Tab tab) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onTabReselected(TabLayout.Tab tab) { |
||||
|
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
getMenuInflater().inflate(R.menu.menu_source_edit, menu); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onOptionsItemSelected(MenuItem item) { |
||||
if (item.getItemId() == R.id.action_save) { |
||||
BookSource source = getSource(); |
||||
if (checkSource(source)) { |
||||
BookSourceManager.addBookSource(source); |
||||
this.source = source; |
||||
setResult(Activity.RESULT_OK); |
||||
ToastUtils.showSuccess("书源保存成功"); |
||||
} |
||||
return true; |
||||
} else if (item.getItemId() == R.id.action_debug_search) { |
||||
debug(item.getTitle(), DebugEntity.SEARCH); |
||||
} else if (item.getItemId() == R.id.action_debug_info) { |
||||
debug(item.getTitle(), DebugEntity.INFO); |
||||
} else if (item.getItemId() == R.id.action_debug_toc) { |
||||
debug(item.getTitle(), DebugEntity.TOC); |
||||
} else if (item.getItemId() == R.id.action_debug_content) { |
||||
debug(item.getTitle(), DebugEntity.CONTENT); |
||||
} |
||||
return super.onOptionsItemSelected(item); |
||||
} |
||||
|
||||
private void debug(CharSequence title, int debugMode) { |
||||
String hint = "请输入URL"; |
||||
if (debugMode == DebugEntity.SEARCH) { |
||||
hint = "请输入关键词"; |
||||
} |
||||
BookSource source = getSource(); |
||||
DebugEntity debugEntity = new DebugEntity(); |
||||
debugEntity.setDebugMode(debugMode); |
||||
debugEntity.setBookSource(source); |
||||
MyAlertDialog.createInputDia(this, String.valueOf(title), hint, |
||||
"", true, 500, text -> { |
||||
if (debugMode == DebugEntity.SEARCH) { |
||||
try { |
||||
MatcherCrawler sc = new MatcherCrawler(source); |
||||
if (!sc.getSearchCharset().toLowerCase().equals("utf-8")) { |
||||
text = URLEncoder.encode(text, sc.getSearchCharset()); |
||||
} |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
debugEntity.setUrl(source.getSearchRule().getSearchUrl().replace("{key}", text)); |
||||
}else { |
||||
debugEntity.setUrl(text); |
||||
} |
||||
}, (dialog, which) -> { |
||||
Intent intent = new Intent(this, SourceDebugActivity.class); |
||||
intent.putExtra("debugEntity", debugEntity); |
||||
startActivity(intent); |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void finish() { |
||||
BookSource source = getSource(); |
||||
if (!source.equals(this.source)) { |
||||
DialogCreator.createThreeButtonDialog(this, "退出" |
||||
, "当前书源已更改,是否保存?", true, |
||||
"直接退出", "取消", "保存并退出", |
||||
(dialog, which) -> super.finish(), null, |
||||
(dialog, which) -> { |
||||
if (checkSource(source)) { |
||||
BookSourceManager.addBookSource(source); |
||||
setResult(Activity.RESULT_OK); |
||||
super.finish(); |
||||
} |
||||
} |
||||
); |
||||
} else { |
||||
super.finish(); |
||||
} |
||||
} |
||||
|
||||
private void setEditEntities(int tabPosition) { |
||||
switch (tabPosition) { |
||||
case 1: |
||||
adapter.refreshItems(searchEntities); |
||||
break; |
||||
case 2: |
||||
adapter.refreshItems(infoEntities); |
||||
break; |
||||
case 3: |
||||
adapter.refreshItems(tocEntities); |
||||
break; |
||||
case 4: |
||||
adapter.refreshItems(contentEntities); |
||||
break; |
||||
default: |
||||
adapter.refreshItems(sourceEntities); |
||||
break; |
||||
} |
||||
binding.recyclerView.scrollToPosition(0); |
||||
} |
||||
|
||||
private void upRecyclerView() { |
||||
initEntities(source); |
||||
binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0)); |
||||
setEditEntities(0); |
||||
} |
||||
|
||||
private BookSource getSource() { |
||||
BookSource source = entityUtil.getSource(this.source, sourceEntities); |
||||
source.setEnable(binding.cbSourceEnable.isChecked()); |
||||
String sourceType = APPCONST.MATCHER; |
||||
switch (binding.sSourceType.getSelectedItemPosition()){ |
||||
case 0: |
||||
sourceType = APPCONST.MATCHER; |
||||
break; |
||||
case 1: |
||||
sourceType = APPCONST.XPATH; |
||||
break; |
||||
case 2: |
||||
sourceType = APPCONST.JSON_PATH; |
||||
break; |
||||
} |
||||
source.setSourceType(sourceType); |
||||
source.setSearchRule(entityUtil.getSearchRule(searchEntities)); |
||||
source.setInfoRule(entityUtil.getInfoRule(infoEntities)); |
||||
source.setTocRule(entityUtil.getTocRule(tocEntities)); |
||||
source.setContentRule(entityUtil.getContentRule(contentEntities)); |
||||
return source; |
||||
} |
||||
|
||||
private boolean checkSource(BookSource source) { |
||||
if (StringHelper.isEmpty(source.getSourceName()) || |
||||
StringHelper.isEmpty(source.getSourceUrl())) { |
||||
ToastUtils.showWarring("书源URL和名称不能为空"); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,129 @@ |
||||
package xyz.fycz.myreader.ui.adapter; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import xyz.fycz.myreader.base.adapter.BaseListAdapter; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 18:25 |
||||
*/ |
||||
public abstract class BaseSourceAdapter extends BaseListAdapter<BookSource> { |
||||
//记录item是否被选中的Map
|
||||
private HashMap<BookSource, Boolean> mCheckMap = new HashMap<>(); |
||||
private int mCheckedCount = 0; |
||||
|
||||
@Override |
||||
public void refreshItems(List<BookSource> list) { |
||||
mCheckMap.clear(); |
||||
for (BookSource bookSource : list) { |
||||
mCheckMap.put(bookSource, false); |
||||
} |
||||
super.refreshItems(list); |
||||
} |
||||
|
||||
@Override |
||||
public void addItem(BookSource value) { |
||||
mCheckMap.put(value, false); |
||||
super.addItem(value); |
||||
} |
||||
|
||||
@Override |
||||
public void addItem(int index, BookSource value) { |
||||
mCheckMap.put(value, false); |
||||
super.addItem(index, value); |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<BookSource> values) { |
||||
for (BookSource bookSource : values) { |
||||
mCheckMap.put(bookSource, false); |
||||
} |
||||
super.addItems(values); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItem(BookSource value) { |
||||
mCheckMap.remove(value); |
||||
super.removeItem(value); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<BookSource> value) { |
||||
for (BookSource bookSource : value) { |
||||
mCheckMap.remove(bookSource); |
||||
--mCheckedCount; |
||||
} |
||||
super.removeItems(value); |
||||
} |
||||
|
||||
//设置点击切换
|
||||
public void setCheckedItem(int pos) { |
||||
BookSource bookSource = getItem(pos); |
||||
boolean isSelected = mCheckMap.get(bookSource); |
||||
if (isSelected) { |
||||
mCheckMap.put(bookSource, false); |
||||
--mCheckedCount; |
||||
} else { |
||||
mCheckMap.put(bookSource, true); |
||||
++mCheckedCount; |
||||
} |
||||
notifyItemChanged(pos); |
||||
} |
||||
|
||||
public void setCheckedAll(boolean isChecked) { |
||||
Set<Map.Entry<BookSource, Boolean>> entrys = mCheckMap.entrySet(); |
||||
mCheckedCount = 0; |
||||
for (Map.Entry<BookSource, Boolean> entry : entrys) { |
||||
//如果选中,则增加点击的数量
|
||||
if (isChecked) { |
||||
++mCheckedCount; |
||||
} |
||||
entry.setValue(isChecked); |
||||
} |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
public void reverseChecked() { |
||||
Set<Map.Entry<BookSource, Boolean>> entrys = mCheckMap.entrySet(); |
||||
for (Map.Entry<BookSource, Boolean> entry : entrys) { |
||||
//如果选中,则减少点击的数量
|
||||
if (entry.getValue()) { |
||||
mCheckedCount--; |
||||
}else { |
||||
mCheckedCount++; |
||||
} |
||||
entry.setValue(!entry.getValue()); |
||||
} |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
public boolean getItemIsChecked(int pos) { |
||||
BookSource bookSource = getItem(pos); |
||||
return mCheckMap.get(bookSource); |
||||
} |
||||
|
||||
public List<BookSource> getCheckedBookSources() { |
||||
List<BookSource> bookSources = new ArrayList<>(); |
||||
Set<Map.Entry<BookSource, Boolean>> entrys = mCheckMap.entrySet(); |
||||
for (Map.Entry<BookSource, Boolean> entry : entrys) { |
||||
if (entry.getValue()) { |
||||
bookSources.add(entry.getKey()); |
||||
} |
||||
} |
||||
return bookSources; |
||||
} |
||||
|
||||
public int getCheckedCount() { |
||||
return mCheckedCount; |
||||
} |
||||
|
||||
public HashMap<BookSource, Boolean> getCheckMap() { |
||||
return mCheckMap; |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
package xyz.fycz.myreader.ui.adapter; |
||||
|
||||
|
||||
import android.os.AsyncTask; |
||||
|
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
import androidx.fragment.app.FragmentActivity; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import xyz.fycz.myreader.base.adapter.BaseListAdapter; |
||||
import xyz.fycz.myreader.base.adapter.IViewHolder; |
||||
import xyz.fycz.myreader.greendao.GreenDaoManager; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.ui.adapter.helper.ItemTouchCallback; |
||||
import xyz.fycz.myreader.ui.adapter.holder.BookSourceHolder; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2020/8/12 20:02 |
||||
*/ |
||||
|
||||
public class BookSourceAdapter extends BaseSourceAdapter { |
||||
private final FragmentActivity activity; |
||||
private final OnSwipeListener onSwipeListener; |
||||
private boolean mEditState; |
||||
private final ItemTouchCallback.OnItemTouchListener itemTouchListener = new ItemTouchCallback.OnItemTouchListener() { |
||||
@Override |
||||
public void onSwiped(int adapterPosition) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public boolean onMove(int srcPosition, int targetPosition) { |
||||
Collections.swap(mList, srcPosition, targetPosition); |
||||
notifyItemMoved(srcPosition, targetPosition); |
||||
notifyItemChanged(srcPosition); |
||||
notifyItemChanged(targetPosition); |
||||
AsyncTask.execute(() -> { |
||||
for (int i = 1; i <= mList.size(); i++) { |
||||
mList.get(i - 1).setOrderNum(i); |
||||
} |
||||
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplaceInTx(mList); |
||||
}); |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
public BookSourceAdapter(FragmentActivity activity, OnSwipeListener onSwipeListener) { |
||||
this.activity = activity; |
||||
this.onSwipeListener = onSwipeListener; |
||||
} |
||||
|
||||
@Override |
||||
protected IViewHolder<BookSource> createViewHolder(int viewType) { |
||||
return new BookSourceHolder(activity, this, onSwipeListener); |
||||
} |
||||
|
||||
public ItemTouchCallback.OnItemTouchListener getItemTouchListener() { |
||||
return itemTouchListener; |
||||
} |
||||
|
||||
public boolean ismEditState() { |
||||
return mEditState; |
||||
} |
||||
|
||||
public void setmEditState(boolean mEditState) { |
||||
this.mEditState = mEditState; |
||||
setCheckedAll(false); |
||||
} |
||||
|
||||
public void removeItem(int pos) { |
||||
mList.remove(pos); |
||||
notifyItemRemoved(pos); |
||||
if (pos != mList.size()) |
||||
notifyItemRangeChanged(pos, mList.size() - pos); |
||||
} |
||||
|
||||
public void toTop(int which, BookSource bean) { |
||||
mList.remove(bean); |
||||
notifyItemInserted(0); |
||||
mList.add(0, bean); |
||||
notifyItemRemoved(which); |
||||
notifyItemRangeChanged(0, which + 1); |
||||
} |
||||
|
||||
public interface OnSwipeListener { |
||||
void onDel(int which, BookSource source); |
||||
|
||||
void onTop(int which, BookSource source); |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
package xyz.fycz.myreader.ui.adapter; |
||||
|
||||
import android.widget.Filter; |
||||
import android.widget.Filterable; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.base.adapter.IViewHolder; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.ui.adapter.holder.LocalSourceHolder; |
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 18:27 |
||||
*/ |
||||
public class LocalSourceAdapter extends BaseSourceAdapter implements Filterable { |
||||
|
||||
private List<BookSource> sources; |
||||
|
||||
public LocalSourceAdapter(List<BookSource> sources) { |
||||
this.sources = sources; |
||||
} |
||||
|
||||
@Override |
||||
protected IViewHolder<BookSource> createViewHolder(int viewType) { |
||||
return new LocalSourceHolder(getCheckMap()); |
||||
} |
||||
|
||||
@Override |
||||
public Filter getFilter() { |
||||
return new Filter() { |
||||
@Override |
||||
protected FilterResults performFiltering(CharSequence constraint) { |
||||
String charString = constraint.toString(); |
||||
List<BookSource> mFilterList = new ArrayList<>(); |
||||
if (charString.isEmpty()) { |
||||
//没有过滤的内容,则使用源数据
|
||||
mFilterList = sources; |
||||
} else { |
||||
for (BookSource source : sources) { |
||||
//这里根据需求,添加匹配规则
|
||||
if (source.getSourceName().contains(charString)) |
||||
mFilterList.add(source); |
||||
} |
||||
} |
||||
FilterResults filterResults = new FilterResults(); |
||||
filterResults.values = mFilterList; |
||||
return filterResults; |
||||
} |
||||
|
||||
//把过滤后的值返回出来
|
||||
@Override |
||||
protected void publishResults(CharSequence constraint, FilterResults results) { |
||||
refreshItems((List<BookSource>) results.values); |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -1,37 +1,104 @@ |
||||
package xyz.fycz.myreader.ui.adapter; |
||||
|
||||
import android.app.Activity; |
||||
import android.widget.Filter; |
||||
import android.widget.Filterable; |
||||
|
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.base.adapter.BaseListAdapter; |
||||
import xyz.fycz.myreader.base.adapter.IViewHolder; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.ui.adapter.holder.ReplaceRuleHolder; |
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/1/19 9:51 |
||||
*/ |
||||
public class ReplaceRuleAdapter extends BaseListAdapter<ReplaceRuleBean> { |
||||
public class ReplaceRuleAdapter extends BaseListAdapter<ReplaceRuleBean> implements Filterable { |
||||
private AppCompatActivity activity; |
||||
private OnDeleteListener onDeleteListener; |
||||
private List<ReplaceRuleBean> beans; |
||||
private OnSwipeListener onSwipeListener; |
||||
|
||||
public ReplaceRuleAdapter(AppCompatActivity activity, OnDeleteListener onDeleteListener) { |
||||
public ReplaceRuleAdapter(AppCompatActivity activity, List<ReplaceRuleBean> beans, OnSwipeListener onSwipeListener) { |
||||
this.activity = activity; |
||||
this.onDeleteListener = onDeleteListener; |
||||
this.beans = beans; |
||||
this.onSwipeListener = onSwipeListener; |
||||
} |
||||
|
||||
@Override |
||||
protected IViewHolder<ReplaceRuleBean> createViewHolder(int viewType) { |
||||
return new ReplaceRuleHolder(activity, onDeleteListener); |
||||
return new ReplaceRuleHolder(activity, onSwipeListener); |
||||
} |
||||
|
||||
public void removeItem(int pos){ |
||||
public void removeItem(int pos) { |
||||
mList.remove(pos); |
||||
notifyItemRemoved(pos); |
||||
if (pos != mList.size()) |
||||
notifyItemRangeChanged(pos, mList.size() - pos); |
||||
} |
||||
|
||||
public void toTop(int which, ReplaceRuleBean bean) { |
||||
mList.remove(bean); |
||||
notifyItemInserted(0); |
||||
mList.add(0, bean); |
||||
notifyItemRemoved(which); |
||||
notifyItemRangeChanged(0, which + 1); |
||||
} |
||||
|
||||
public void setBeans(List<ReplaceRuleBean> beans) { |
||||
this.beans = beans; |
||||
} |
||||
|
||||
/** |
||||
* 过滤器,实现搜索 |
||||
* |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public Filter getFilter() { |
||||
return new Filter() { |
||||
@Override |
||||
protected FilterResults performFiltering(CharSequence constraint) { |
||||
String charString = constraint.toString(); |
||||
List<ReplaceRuleBean> mFilterList = new ArrayList<>(); |
||||
if (charString.isEmpty()) { |
||||
//没有过滤的内容,则使用源数据
|
||||
mFilterList = beans; |
||||
} else { |
||||
for (ReplaceRuleBean bean : beans) { |
||||
//这里根据需求,添加匹配规则
|
||||
if (StringHelper.isEmpty(bean.getReplaceSummary())) { |
||||
if (bean.getRegex().contains(charString) || |
||||
bean.getReplacement().contains(charString)) { |
||||
mFilterList.add(bean); |
||||
} |
||||
} else { |
||||
if (bean.getReplaceSummary().contains(charString)) { |
||||
mFilterList.add(bean); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
FilterResults filterResults = new FilterResults(); |
||||
filterResults.values = mFilterList; |
||||
return filterResults; |
||||
} |
||||
|
||||
//把过滤后的值返回出来
|
||||
@Override |
||||
protected void publishResults(CharSequence constraint, FilterResults results) { |
||||
refreshItems((List<ReplaceRuleBean>) results.values); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public interface OnSwipeListener { |
||||
void onDel(int which, ReplaceRuleBean bean); |
||||
|
||||
public interface OnDeleteListener{ |
||||
void success(int which); |
||||
void onTop(int which, ReplaceRuleBean bean); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,17 @@ |
||||
package xyz.fycz.myreader.ui.adapter; |
||||
|
||||
import xyz.fycz.myreader.base.adapter.BaseListAdapter; |
||||
import xyz.fycz.myreader.base.adapter.IViewHolder; |
||||
import xyz.fycz.myreader.entity.sourceedit.EditEntity; |
||||
import xyz.fycz.myreader.ui.adapter.holder.SourceEditHolder; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/9 10:08 |
||||
*/ |
||||
public class SourceEditAdapter extends BaseListAdapter<EditEntity> { |
||||
@Override |
||||
protected IViewHolder<EditEntity> createViewHolder(int viewType) { |
||||
return new SourceEditHolder(); |
||||
} |
||||
} |
@ -0,0 +1,160 @@ |
||||
package xyz.fycz.myreader.ui.adapter.helper; |
||||
|
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.GridLayoutManager; |
||||
import androidx.recyclerview.widget.ItemTouchHelper; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; |
||||
import androidx.viewpager.widget.ViewPager; |
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/9 10:08 |
||||
*/ |
||||
|
||||
public class ItemTouchCallback extends ItemTouchHelper.Callback { |
||||
|
||||
private ViewPager viewPager; |
||||
|
||||
public void setViewPager(ViewPager viewPager) { |
||||
this.viewPager = viewPager; |
||||
} |
||||
|
||||
/** |
||||
* Item操作的回调 |
||||
*/ |
||||
private OnItemTouchListener onItemTouchListener; |
||||
|
||||
/** |
||||
* 是否可以拖拽 |
||||
*/ |
||||
private boolean isCanDrag = false; |
||||
/** |
||||
* 是否可以被滑动 |
||||
*/ |
||||
private boolean isCanSwipe = false; |
||||
|
||||
/** |
||||
* 设置Item操作的回调,去更新UI和数据源 |
||||
*/ |
||||
public void setOnItemTouchListener(OnItemTouchListener onItemTouchListener) { |
||||
this.onItemTouchListener = onItemTouchListener; |
||||
} |
||||
|
||||
/** |
||||
* 设置是否可以被拖拽 |
||||
* |
||||
* @param canDrag 是true,否false |
||||
*/ |
||||
public void setDragEnable(boolean canDrag) { |
||||
isCanDrag = canDrag; |
||||
} |
||||
|
||||
/** |
||||
* 设置是否可以被滑动 |
||||
* |
||||
* @param canSwipe 是true,否false |
||||
*/ |
||||
public void setSwipeEnable(boolean canSwipe) { |
||||
isCanSwipe = canSwipe; |
||||
} |
||||
|
||||
/** |
||||
* 当Item被长按的时候是否可以被拖拽 |
||||
*/ |
||||
@Override |
||||
public boolean isLongPressDragEnabled() { |
||||
return isCanDrag; |
||||
} |
||||
|
||||
/** |
||||
* Item是否可以被滑动(H:左右滑动,V:上下滑动) |
||||
*/ |
||||
@Override |
||||
public boolean isItemViewSwipeEnabled() { |
||||
return isCanSwipe; |
||||
} |
||||
|
||||
/** |
||||
* 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向 |
||||
*/ |
||||
@Override |
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { |
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); |
||||
if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager
|
||||
// flag如果值是0,相当于这个功能被关闭
|
||||
int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN; |
||||
int swipeFlag = 0; |
||||
// create make
|
||||
return makeMovementFlags(dragFlag, swipeFlag); |
||||
} else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager
|
||||
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; |
||||
int orientation = linearLayoutManager.getOrientation(); |
||||
|
||||
int dragFlag = 0; |
||||
int swipeFlag = 0; |
||||
|
||||
// 为了方便理解,相当于分为横着的ListView和竖着的ListView
|
||||
if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局
|
||||
swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; |
||||
dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; |
||||
} else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局,相当于ListView
|
||||
dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; |
||||
swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; |
||||
} |
||||
return makeMovementFlags(dragFlag, swipeFlag); |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
/** |
||||
* 当Item被拖拽的时候被回调 |
||||
* |
||||
* @param recyclerView recyclerView |
||||
* @param srcViewHolder 拖拽的ViewHolder |
||||
* @param targetViewHolder 目的地的viewHolder |
||||
*/ |
||||
@Override |
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder srcViewHolder, @NonNull RecyclerView.ViewHolder targetViewHolder) { |
||||
if (onItemTouchListener != null) { |
||||
return onItemTouchListener.onMove(srcViewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition()); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { |
||||
if (onItemTouchListener != null) { |
||||
onItemTouchListener.onSwiped(viewHolder.getAdapterPosition()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) { |
||||
super.onSelectedChanged(viewHolder, actionState); |
||||
final boolean swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG; |
||||
if (viewPager != null) { |
||||
viewPager.requestDisallowInterceptTouchEvent(swiping); |
||||
} |
||||
} |
||||
|
||||
public interface OnItemTouchListener { |
||||
/** |
||||
* 当某个Item被滑动删除的时候 |
||||
* |
||||
* @param adapterPosition item的position |
||||
*/ |
||||
void onSwiped(int adapterPosition); |
||||
|
||||
/** |
||||
* 当两个Item位置互换的时候被回调 |
||||
* |
||||
* @param srcPosition 拖拽的item的position |
||||
* @param targetPosition 目的地的Item的position |
||||
* @return 开发者处理了操作应该返回true,开发者没有处理就返回false |
||||
*/ |
||||
boolean onMove(int srcPosition, int targetPosition); |
||||
} |
||||
} |
@ -0,0 +1,185 @@ |
||||
package xyz.fycz.myreader.ui.adapter.holder; |
||||
|
||||
import android.content.Intent; |
||||
import android.view.View; |
||||
import android.widget.Button; |
||||
import android.widget.CheckBox; |
||||
import android.widget.ImageView; |
||||
import android.widget.RelativeLayout; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
import androidx.fragment.app.FragmentActivity; |
||||
|
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.ObservableOnSubscribe; |
||||
import io.reactivex.android.schedulers.AndroidSchedulers; |
||||
import io.reactivex.annotations.NonNull; |
||||
import io.reactivex.schedulers.Schedulers; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.adapter.ViewHolderImpl; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.base.observer.MySingleObserver; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.model.ReplaceRuleManager; |
||||
import xyz.fycz.myreader.model.source.BookSourceManager; |
||||
import xyz.fycz.myreader.ui.activity.SourceEditActivity; |
||||
import xyz.fycz.myreader.ui.adapter.BookSourceAdapter; |
||||
import xyz.fycz.myreader.ui.dialog.ReplaceDialog; |
||||
import xyz.fycz.myreader.util.ShareUtils; |
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 16:52 |
||||
*/ |
||||
public class BookSourceHolder extends ViewHolderImpl<BookSource> { |
||||
private FragmentActivity activity; |
||||
private BookSourceAdapter adapter; |
||||
private HashMap<BookSource, Boolean> mCheckMap; |
||||
private BookSourceAdapter.OnSwipeListener onSwipeListener; |
||||
private SwipeMenuLayout layout; |
||||
private RelativeLayout rlContent; |
||||
private CheckBox cbSourceSelect; |
||||
private TextView tvSourceName; |
||||
private ImageView ivSwipeLeft; |
||||
private Button btTop; |
||||
private Button btBan; |
||||
private Button btShare; |
||||
private Button btDelete; |
||||
|
||||
public BookSourceHolder(FragmentActivity activity, BookSourceAdapter adapter, BookSourceAdapter.OnSwipeListener onSwipeListener) { |
||||
this.activity = activity; |
||||
this.adapter = adapter; |
||||
this.onSwipeListener = onSwipeListener; |
||||
mCheckMap = adapter.getCheckMap(); |
||||
} |
||||
|
||||
@Override |
||||
protected int getItemLayoutId() { |
||||
return R.layout.item_book_source; |
||||
} |
||||
|
||||
@Override |
||||
public void initView() { |
||||
layout = (SwipeMenuLayout) getItemView(); |
||||
rlContent = findById(R.id.rl_content); |
||||
cbSourceSelect = findById(R.id.cb_source_select); |
||||
tvSourceName = findById(R.id.tv_source_name); |
||||
ivSwipeLeft = findById(R.id.iv_swipe_left); |
||||
btTop = findById(R.id.bt_top); |
||||
btBan = findById(R.id.bt_ban); |
||||
btShare = findById(R.id.bt_share); |
||||
btDelete = findById(R.id.btnDelete); |
||||
} |
||||
|
||||
@Override |
||||
public void onBind(BookSource data, int pos) { |
||||
banOrUse(data); |
||||
initClick(data, pos); |
||||
layout.setSwipeEnable(!adapter.ismEditState()); |
||||
if (adapter.ismEditState()) { |
||||
cbSourceSelect.setVisibility(View.VISIBLE); |
||||
ivSwipeLeft.setVisibility(View.GONE); |
||||
} else { |
||||
cbSourceSelect.setVisibility(View.GONE); |
||||
ivSwipeLeft.setVisibility(View.VISIBLE); |
||||
} |
||||
cbSourceSelect.setChecked(mCheckMap.get(data)); |
||||
} |
||||
|
||||
private void initClick(BookSource data, int pos) { |
||||
rlContent.setOnClickListener(v -> { |
||||
if (adapter.ismEditState()) { |
||||
adapter.setCheckedItem(pos); |
||||
} else { |
||||
Intent intent = new Intent(activity, SourceEditActivity.class); |
||||
intent.putExtra(APPCONST.BOOK_SOURCE, data); |
||||
activity.getSupportFragmentManager().getFragments().get(1) |
||||
.startActivityForResult(intent, APPCONST.REQUEST_EDIT_BOOK_SOURCE); |
||||
} |
||||
}); |
||||
btTop.setOnClickListener(v -> { |
||||
((SwipeMenuLayout) getItemView()).smoothClose(); |
||||
BookSourceManager.toTop(data) |
||||
.subscribe(new MySingleObserver<Boolean>() { |
||||
@Override |
||||
public void onSuccess(@NonNull Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
onSwipeListener.onTop(pos, data); |
||||
} |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
btBan.setOnClickListener(v -> { |
||||
((SwipeMenuLayout) getItemView()).smoothClose(); |
||||
data.setEnable(!data.getEnable()); |
||||
BookSourceManager.saveData(data) |
||||
.subscribe(new MySingleObserver<Boolean>() { |
||||
@Override |
||||
public void onSuccess(@NonNull Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
banOrUse(data); |
||||
} |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
btShare.setOnClickListener(v -> { |
||||
((SwipeMenuLayout) getItemView()).smoothClose(); |
||||
ShareUtils.share(activity, GsonExtensionsKt.getGSON().toJson(data)); |
||||
}); |
||||
|
||||
btDelete.setOnClickListener(v -> { |
||||
((SwipeMenuLayout) getItemView()).smoothClose(); |
||||
Observable.create((ObservableOnSubscribe<Boolean>) e -> { |
||||
BookSourceManager.removeBookSource(data); |
||||
e.onNext(true); |
||||
}).subscribeOn(Schedulers.io()) |
||||
.observeOn(AndroidSchedulers.mainThread()) |
||||
.subscribe(new MyObserver<Boolean>() { |
||||
@Override |
||||
public void onNext(Boolean aBoolean) { |
||||
onSwipeListener.onDel(pos, data); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("删除失败"); |
||||
} |
||||
}); |
||||
|
||||
}); |
||||
} |
||||
|
||||
private void banOrUse(BookSource data) { |
||||
if (data.getEnable()) { |
||||
tvSourceName.setTextColor(getContext().getResources().getColor(R.color.textPrimary)); |
||||
if (!StringHelper.isEmpty(data.getSourceGroup())) { |
||||
tvSourceName.setText(String.format("%s [%s]", data.getSourceName(), data.getSourceGroup())); |
||||
} else { |
||||
tvSourceName.setText(data.getSourceName()); |
||||
} |
||||
btBan.setText(getContext().getString(R.string.ban)); |
||||
} else { |
||||
tvSourceName.setTextColor(getContext().getResources().getColor(R.color.textSecondary)); |
||||
if (!StringHelper.isEmpty(data.getSourceGroup())) { |
||||
tvSourceName.setText(String.format("(禁用中)%s [%s]", data.getSourceName(), data.getSourceGroup())); |
||||
} else { |
||||
tvSourceName.setText(String.format("(禁用中)%s", data.getSourceName())); |
||||
} |
||||
btBan.setText(R.string.enable_use); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,67 @@ |
||||
package xyz.fycz.myreader.ui.adapter.holder; |
||||
|
||||
import android.widget.CheckBox; |
||||
import android.widget.RelativeLayout; |
||||
import android.widget.TextView; |
||||
|
||||
import java.util.HashMap; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.adapter.ViewHolderImpl; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.util.StringHelper; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 18:28 |
||||
*/ |
||||
public class LocalSourceHolder extends ViewHolderImpl<BookSource> { |
||||
private HashMap<BookSource, Boolean> mCheckMap; |
||||
private CheckBox cbSource; |
||||
private TextView tvEnable; |
||||
private TextView tvDisable; |
||||
private TextView tvCheck; |
||||
|
||||
public LocalSourceHolder(HashMap<BookSource, Boolean> mCheckMap) { |
||||
this.mCheckMap = mCheckMap; |
||||
} |
||||
|
||||
@Override |
||||
protected int getItemLayoutId() { |
||||
return R.layout.item_local_source; |
||||
} |
||||
|
||||
@Override |
||||
public void initView() { |
||||
cbSource = findById(R.id.cb_source); |
||||
tvEnable = findById(R.id.tv_enable); |
||||
tvDisable = findById(R.id.tv_disable); |
||||
tvCheck = findById(R.id.tv_check); |
||||
} |
||||
|
||||
@Override |
||||
public void onBind(BookSource data, int pos) { |
||||
banOrUse(data); |
||||
cbSource.setChecked(mCheckMap.get(data)); |
||||
tvEnable.setOnClickListener(v -> { |
||||
data.setEnable(true); |
||||
banOrUse(data); |
||||
}); |
||||
tvDisable.setOnClickListener(v -> { |
||||
data.setEnable(false); |
||||
banOrUse(data); |
||||
}); |
||||
tvCheck.setOnClickListener(v -> ToastUtils.showInfo("校验功能即将上线")); |
||||
} |
||||
|
||||
private void banOrUse(BookSource data) { |
||||
if (data.getEnable()) { |
||||
cbSource.setTextColor(getContext().getResources().getColor(R.color.textPrimary)); |
||||
cbSource.setText(data.getSourceName()); |
||||
} else { |
||||
cbSource.setTextColor(getContext().getResources().getColor(R.color.textSecondary)); |
||||
cbSource.setText(String.format("(禁用中)%s", data.getSourceName())); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,79 @@ |
||||
package xyz.fycz.myreader.ui.adapter.holder |
||||
|
||||
import android.text.Editable |
||||
import android.text.TextUtils |
||||
import android.text.TextWatcher |
||||
import android.view.View |
||||
import android.widget.TextView |
||||
import com.google.android.material.textfield.TextInputEditText |
||||
import com.google.android.material.textfield.TextInputLayout |
||||
import xyz.fycz.myreader.R |
||||
import xyz.fycz.myreader.base.adapter.ViewHolderImpl |
||||
import xyz.fycz.myreader.entity.sourceedit.EditEntity |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/9 10:25 |
||||
*/ |
||||
class SourceEditHolder : ViewHolderImpl<EditEntity>() { |
||||
private var textInputLayout: TextInputLayout? = null |
||||
private var editText: TextInputEditText? = null |
||||
private var tvTip: TextView? = null |
||||
|
||||
override fun getItemLayoutId(): Int { |
||||
return R.layout.item_source_edit |
||||
} |
||||
|
||||
override fun initView() { |
||||
textInputLayout = findById(R.id.text_input_layout) |
||||
editText = findById(R.id.edit_text) |
||||
tvTip = findById(R.id.tv_tip) |
||||
} |
||||
|
||||
override fun onBind(data: EditEntity, pos: Int) { |
||||
if (editText?.getTag(R.id.tag1) == null) { |
||||
val listener = object : View.OnAttachStateChangeListener { |
||||
override fun onViewAttachedToWindow(v: View) { |
||||
editText?.isCursorVisible = false |
||||
editText?.isCursorVisible = true |
||||
editText?.isFocusable = true |
||||
editText?.isFocusableInTouchMode = true |
||||
} |
||||
|
||||
override fun onViewDetachedFromWindow(v: View) { |
||||
|
||||
} |
||||
} |
||||
editText?.addOnAttachStateChangeListener(listener) |
||||
editText?.setTag(R.id.tag1, listener) |
||||
} |
||||
editText?.getTag(R.id.tag2)?.let { |
||||
if (it is TextWatcher) { |
||||
editText?.removeTextChangedListener(it) |
||||
} |
||||
} |
||||
textInputLayout?.hint = context.getString(data.hint) |
||||
editText?.setText(data.value) |
||||
if (data.tip.isNullOrEmpty()) { |
||||
tvTip?.visibility = View.GONE |
||||
} else { |
||||
tvTip?.visibility = View.VISIBLE |
||||
tvTip?.text = data.tip |
||||
} |
||||
val textWatcher = object : TextWatcher { |
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { |
||||
|
||||
} |
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { |
||||
|
||||
} |
||||
|
||||
override fun afterTextChanged(s: Editable?) { |
||||
data.value = (s.toString()) |
||||
} |
||||
} |
||||
editText?.addTextChangedListener(textWatcher) |
||||
editText?.setTag(R.id.tag2, textWatcher) |
||||
} |
||||
} |
@ -0,0 +1,432 @@ |
||||
package xyz.fycz.myreader.ui.fragment; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.text.TextUtils; |
||||
import android.view.Gravity; |
||||
import android.view.LayoutInflater; |
||||
import android.view.MenuInflater; |
||||
import android.view.MenuItem; |
||||
import android.view.MotionEvent; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.PopupMenu; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.ItemTouchHelper; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.Single; |
||||
import io.reactivex.SingleEmitter; |
||||
import io.reactivex.SingleOnSubscribe; |
||||
import io.reactivex.annotations.NonNull; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BaseFragment; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.base.observer.MySingleObserver; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.databinding.FragmentImportSourceBinding; |
||||
import xyz.fycz.myreader.greendao.GreenDaoManager; |
||||
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.greendao.gen.BookSourceDao; |
||||
import xyz.fycz.myreader.model.ReplaceRuleManager; |
||||
import xyz.fycz.myreader.model.source.BookSourceManager; |
||||
import xyz.fycz.myreader.ui.activity.BookSourceActivity; |
||||
import xyz.fycz.myreader.ui.activity.SourceEditActivity; |
||||
import xyz.fycz.myreader.ui.adapter.BookSourceAdapter; |
||||
import xyz.fycz.myreader.ui.adapter.helper.ItemTouchCallback; |
||||
import xyz.fycz.myreader.ui.dialog.DialogCreator; |
||||
import xyz.fycz.myreader.ui.dialog.MyAlertDialog; |
||||
import xyz.fycz.myreader.util.ShareUtils; |
||||
import xyz.fycz.myreader.util.SharedPreUtils; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.ClipBoardUtil; |
||||
import xyz.fycz.myreader.util.utils.FileUtils; |
||||
import xyz.fycz.myreader.util.utils.GsonExtensionsKt; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.widget.DividerItemDecoration; |
||||
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout; |
||||
|
||||
import static android.app.Activity.RESULT_OK; |
||||
import static android.text.TextUtils.isEmpty; |
||||
import static xyz.fycz.myreader.util.UriFileUtil.getPath; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 12:05 |
||||
*/ |
||||
public class DIYSourceFragment extends BaseFragment { |
||||
private FragmentImportSourceBinding binding; |
||||
|
||||
private final BookSourceActivity sourceActivity; |
||||
private List<BookSource> mBookSources; |
||||
private BookSourceAdapter mAdapter; |
||||
private boolean isSearch; |
||||
private PopupMenu featuresMenu; |
||||
private ItemTouchCallback itemTouchCallback; |
||||
|
||||
public DIYSourceFragment() { |
||||
sourceActivity = (BookSourceActivity) getActivity(); |
||||
} |
||||
|
||||
public DIYSourceFragment(BookSourceActivity sourceActivity) { |
||||
this.sourceActivity = sourceActivity; |
||||
} |
||||
|
||||
@Override |
||||
protected View bindView(LayoutInflater inflater, ViewGroup container) { |
||||
binding = FragmentImportSourceBinding.inflate(inflater, container, false); |
||||
return binding.getRoot(); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
mAdapter = new BookSourceAdapter(getActivity(), new BookSourceAdapter.OnSwipeListener() { |
||||
@Override |
||||
public void onDel(int which, BookSource bean) { |
||||
mBookSources.remove(bean); |
||||
mAdapter.removeItem(which); |
||||
} |
||||
|
||||
@Override |
||||
public void onTop(int which, BookSource bean) { |
||||
if (which > 0 && which < mBookSources.size()) { |
||||
mBookSources.remove(bean); |
||||
mBookSources.add(0, bean); |
||||
mAdapter.toTop(which, bean); |
||||
} |
||||
} |
||||
}); |
||||
refreshSources(); |
||||
} |
||||
|
||||
@Override |
||||
protected void initWidget(Bundle savedInstanceState) { |
||||
super.initWidget(savedInstanceState); |
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); |
||||
binding.recyclerView.setAdapter(mAdapter); |
||||
//设置分割线
|
||||
binding.recyclerView.addItemDecoration(new DividerItemDecoration(getContext())); |
||||
//设置拖拽
|
||||
itemTouchCallback = new ItemTouchCallback(); |
||||
itemTouchCallback.setOnItemTouchListener(mAdapter.getItemTouchListener()); |
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchCallback); |
||||
itemTouchHelper.attachToRecyclerView(binding.recyclerView); |
||||
itemTouchCallback.setDragEnable(false); |
||||
} |
||||
|
||||
@SuppressLint("ClickableViewAccessibility") |
||||
@Override |
||||
protected void initClick() { |
||||
super.initClick(); |
||||
binding.recyclerView.setOnTouchListener((v, event) -> { |
||||
if (event.getAction() == MotionEvent.ACTION_UP) { |
||||
SwipeMenuLayout viewCache = SwipeMenuLayout.getViewCache(); |
||||
if (null != viewCache) { |
||||
viewCache.smoothClose(); |
||||
} |
||||
} |
||||
return false; |
||||
}); |
||||
|
||||
binding.tvNewSource.setOnClickListener(v -> |
||||
startActivityForResult(new Intent(getContext(), SourceEditActivity.class), |
||||
APPCONST.REQUEST_EDIT_BOOK_SOURCE)); |
||||
binding.tvImportSource.setOnClickListener(v -> { |
||||
MyAlertDialog.build(getContext()) |
||||
.setTitle("导入书源") |
||||
.setItems(R.array.import_rule, (dialog, which) -> { |
||||
if (which == 0) { |
||||
String text = ClipBoardUtil.paste(getContext()); |
||||
if (!isEmpty(text)) { |
||||
importDataS(text); |
||||
} else { |
||||
ToastUtils.showError("剪切板内容为空,导入失败"); |
||||
} |
||||
} else if (which == 1) { |
||||
ToastUtils.showInfo("请选择书源JSON文件"); |
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
||||
intent.addCategory(Intent.CATEGORY_OPENABLE); |
||||
intent.setType("application/json"); |
||||
startActivityForResult(intent, APPCONST.REQUEST_IMPORT_BOOK_SOURCE); |
||||
} else { |
||||
String[] url = new String[1]; |
||||
MyAlertDialog.createInputDia(getContext(), "网络导入", |
||||
"请输入网址", "", true, 200, |
||||
text -> url[0] = text, |
||||
(dialog1, which1) -> importDataS(url[0])); |
||||
|
||||
} |
||||
}).show(); |
||||
}); |
||||
|
||||
binding.ivGroup.setOnClickListener(this::showSourceGroupMenu); |
||||
|
||||
binding.ivMenu.setOnClickListener(v -> { |
||||
if (featuresMenu == null) { |
||||
initFeaturesMenu(v); |
||||
} |
||||
featuresMenu.show(); |
||||
}); |
||||
} |
||||
|
||||
public void importDataS(String text) { |
||||
Observable<List<BookSource>> observable = BookSourceManager.importSource(text); |
||||
if (observable != null) { |
||||
observable.subscribe(new MyObserver<List<BookSource>>() { |
||||
@Override |
||||
public void onNext(List<BookSource> sources) { |
||||
int size = sources.size(); |
||||
if (size > 0) { |
||||
refreshSources(); |
||||
ToastUtils.showSuccess(String.format("成功导入%s个书源", size)); |
||||
} else { |
||||
ToastUtils.showError("格式不对"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("格式不对"); |
||||
} |
||||
}); |
||||
} else { |
||||
ToastUtils.showError("导入失败"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 刷新书源 |
||||
*/ |
||||
private void refreshSources() { |
||||
Single.create((SingleOnSubscribe<List<BookSource>>) emitter -> { |
||||
List<BookSource> sourceBeanList; |
||||
if (isSearch) { |
||||
if (sourceActivity.getSearchView().getQuery().toString().equals("enabled")) { |
||||
sourceBeanList = GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceEName.isNull()) |
||||
.where(BookSourceDao.Properties.Enable.eq(1)) |
||||
.orderRaw(BookSourceManager.getBookSourceSort()) |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} else { |
||||
String term = "%" + sourceActivity.getSearchView().getQuery() + "%"; |
||||
sourceBeanList = GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder() |
||||
.where(BookSourceDao.Properties.SourceEName.isNull()) |
||||
.whereOr(BookSourceDao.Properties.SourceName.like(term), |
||||
BookSourceDao.Properties.SourceGroup.like(term), |
||||
BookSourceDao.Properties.SourceUrl.like(term)) |
||||
.orderRaw(BookSourceManager.getBookSourceSort()) |
||||
.orderAsc(BookSourceDao.Properties.OrderNum) |
||||
.list(); |
||||
} |
||||
} else { |
||||
sourceBeanList = BookSourceManager.getAllNoLocalSource(); |
||||
} |
||||
emitter.onSuccess(sourceBeanList); |
||||
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<List<BookSource>>() { |
||||
@Override |
||||
public void onSuccess(@NonNull List<BookSource> sources) { |
||||
mBookSources = sources; |
||||
mAdapter.refreshItems(mBookSources); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { |
||||
if (resultCode == RESULT_OK) { |
||||
if (requestCode == APPCONST.REQUEST_IMPORT_BOOK_SOURCE) { |
||||
String path = getPath(getContext(), data.getData()); |
||||
String json = FileUtils.readText(path); |
||||
if (!isEmpty(json)) { |
||||
importDataS(json); |
||||
} else { |
||||
ToastUtils.showError("文件读取失败"); |
||||
} |
||||
} else if (requestCode == APPCONST.REQUEST_EDIT_BOOK_SOURCE) { |
||||
refreshSources(); |
||||
} |
||||
} |
||||
super.onActivityResult(requestCode, resultCode, data); |
||||
} |
||||
|
||||
/** |
||||
* 显示书源分组菜单 |
||||
*/ |
||||
public void showSourceGroupMenu(View view) { |
||||
PopupMenu popupMenu = new PopupMenu(sourceActivity, view, Gravity.END); |
||||
List<String> groupList = BookSourceManager.getNoLocalGroupList(); |
||||
popupMenu.getMenu().add(0, 0, 0, "所有书源"); |
||||
for (int i = 0; i < groupList.size(); i++) { |
||||
popupMenu.getMenu().add(0, 0, i + 1, groupList.get(i)); |
||||
} |
||||
popupMenu.setOnMenuItemClickListener(menuItem -> { |
||||
if (menuItem.getOrder() > 0) { |
||||
sourceActivity.getSearchView().onActionViewExpanded(); |
||||
sourceActivity.getSearchView().clearFocus(); |
||||
sourceActivity.getSearchView().setQuery(menuItem.getTitle(), false); |
||||
} else { |
||||
sourceActivity.getSearchView().onActionViewCollapsed(); |
||||
} |
||||
return true; |
||||
}); |
||||
popupMenu.show(); |
||||
} |
||||
|
||||
private void initFeaturesMenu(View view) { |
||||
featuresMenu = new PopupMenu(sourceActivity, view, Gravity.END); |
||||
//获取菜单填充器
|
||||
MenuInflater inflater = featuresMenu.getMenuInflater(); |
||||
//填充菜单
|
||||
inflater.inflate(R.menu.menu_import_source, featuresMenu.getMenu()); |
||||
featuresMenu.setOnMenuItemClickListener(item -> { |
||||
int itemId = item.getItemId(); |
||||
if (itemId == R.id.action_edit) { |
||||
featuresMenu.getMenu().setGroupVisible(R.id.edit_mode, true); |
||||
featuresMenu.getMenu().setGroupVisible(R.id.non_edit_mode, false); |
||||
mAdapter.setmEditState(true); |
||||
itemTouchCallback.setDragEnable(true); |
||||
} else if (itemId == R.id.action_finish) { |
||||
featuresMenu.getMenu().setGroupVisible(R.id.edit_mode, false); |
||||
featuresMenu.getMenu().setGroupVisible(R.id.non_edit_mode, true); |
||||
mAdapter.setmEditState(false); |
||||
itemTouchCallback.setDragEnable(false); |
||||
} else if (itemId == R.id.action_export) { |
||||
exportSources(mBookSources); |
||||
} else if (itemId == R.id.action_share) { |
||||
shareSources(mBookSources); |
||||
} else if (itemId == R.id.action_reverse) { |
||||
reverseSources(mBookSources); |
||||
} else if (itemId == R.id.action_delete) { |
||||
deleteSources(mBookSources, true); |
||||
} else if (itemId == R.id.action_select_all) { |
||||
mAdapter.setCheckedAll(true); |
||||
} else if (itemId == R.id.action_reverse_selected) { |
||||
mAdapter.reverseChecked(); |
||||
} else if (itemId == R.id.action_export_selected) { |
||||
List<BookSource> bookSources = mAdapter.getCheckedBookSources(); |
||||
exportSources(bookSources); |
||||
} else if (itemId == R.id.action_share_selected) { |
||||
List<BookSource> bookSources = mAdapter.getCheckedBookSources(); |
||||
shareSources(bookSources); |
||||
} else if (itemId == R.id.action_reverse_enable_selected) { |
||||
List<BookSource> bookSources = mAdapter.getCheckedBookSources(); |
||||
reverseSources(bookSources); |
||||
} else if (itemId == R.id.action_delete_selected) { |
||||
List<BookSource> bookSources = mAdapter.getCheckedBookSources(); |
||||
deleteSources(bookSources, false); |
||||
} |
||||
return true; |
||||
}); |
||||
} |
||||
|
||||
|
||||
private void exportSources(List<BookSource> sources) { |
||||
if (sources == null || sources.size() == 0) { |
||||
ToastUtils.showWarring("当前没有选择任何书源,无法导出!"); |
||||
return; |
||||
} |
||||
Single.create((SingleOnSubscribe<Boolean>) emitter -> { |
||||
emitter.onSuccess(FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(sources), |
||||
FileUtils.getFile(APPCONST.FILE_DIR + "BookSources.json"))); |
||||
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<Boolean>() { |
||||
@Override |
||||
public void onSuccess(@NonNull Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
DialogCreator.createTipDialog(sourceActivity, |
||||
"书源导出成功,导出位置:" + APPCONST.FILE_DIR + "BookSources.json"); |
||||
} else { |
||||
ToastUtils.showError("书源导出失败"); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void shareSources(List<BookSource> bookSources) { |
||||
if (bookSources == null || bookSources.size() == 0) { |
||||
ToastUtils.showWarring("当前没有选择任何书源,无法分享!"); |
||||
return; |
||||
} |
||||
Single.create((SingleOnSubscribe<File>) emitter -> { |
||||
File share = FileUtils.getFile(APPCONST.SHARE_FILE_DIR + "ShareBookSources.json"); |
||||
if (FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(bookSources), share)){ |
||||
emitter.onSuccess(share); |
||||
}else { |
||||
emitter.onError(new Exception("书源文件写出失败")); |
||||
} |
||||
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<File>() { |
||||
@Override |
||||
public void onSuccess(@NonNull File share) { |
||||
ShareUtils.share(sourceActivity, share, "书源分享", "text/plain"); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("书源分享失败\n" + e.getLocalizedMessage()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void reverseSources(List<BookSource> mBookSources) { |
||||
for (BookSource source : mBookSources) { |
||||
source.setEnable(!source.getEnable()); |
||||
} |
||||
BookSourceManager.saveDatas(mBookSources); |
||||
mAdapter.notifyDataSetChanged(); |
||||
} |
||||
|
||||
private void deleteSources(List<BookSource> mBookSources, boolean isDisabled) { |
||||
if (mBookSources == null || mBookSources.size() == 0) { |
||||
ToastUtils.showWarring("当前没有选择任何书源,无法删除!"); |
||||
return; |
||||
} |
||||
String title, msg, successTip; |
||||
if (isDisabled) { |
||||
title = "删除禁用书源"; |
||||
msg = "确定要删除所有禁用书源吗?"; |
||||
successTip = "禁用书源删除成功"; |
||||
} else { |
||||
title = "删除选中书源"; |
||||
msg = "确定要删除所有选中书源吗?"; |
||||
successTip = "选中书源删除成功"; |
||||
} |
||||
DialogCreator.createCommonDialog(sourceActivity, title, |
||||
msg, true, |
||||
(dialog, which) -> { |
||||
List<BookSource> sources = new ArrayList<>(); |
||||
if (isDisabled) { |
||||
for (BookSource source : mBookSources) { |
||||
if (!source.getEnable()) { |
||||
sources.add(source); |
||||
} |
||||
} |
||||
} else { |
||||
sources.addAll(mBookSources); |
||||
} |
||||
BookSourceManager.removeBookSources(sources); |
||||
mBookSources.removeAll(sources); |
||||
mAdapter.removeItems(sources); |
||||
ToastUtils.showSuccess(successTip); |
||||
}, null); |
||||
} |
||||
|
||||
public void startSearch(String newText) { |
||||
isSearch = !TextUtils.isEmpty(newText); |
||||
refreshSources(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,125 @@ |
||||
package xyz.fycz.myreader.ui.fragment; |
||||
|
||||
import android.os.Bundle; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Single; |
||||
import io.reactivex.SingleEmitter; |
||||
import io.reactivex.SingleOnSubscribe; |
||||
import io.reactivex.annotations.NonNull; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BaseFragment; |
||||
import xyz.fycz.myreader.base.observer.MySingleObserver; |
||||
import xyz.fycz.myreader.databinding.FragmentLocalSourceBinding; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.model.source.BookSourceManager; |
||||
import xyz.fycz.myreader.ui.activity.BookSourceActivity; |
||||
import xyz.fycz.myreader.ui.adapter.LocalSourceAdapter; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.widget.DividerItemDecoration; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/2/10 18:46 |
||||
*/ |
||||
public class LocalSourceFragment extends BaseFragment { |
||||
private FragmentLocalSourceBinding binding; |
||||
private BookSourceActivity sourceActivity; |
||||
private List<BookSource> mBookSources; |
||||
private LocalSourceAdapter mAdapter; |
||||
|
||||
public LocalSourceFragment() { |
||||
} |
||||
|
||||
public LocalSourceFragment(BookSourceActivity sourceActivity) { |
||||
this.sourceActivity = sourceActivity; |
||||
} |
||||
|
||||
@Override |
||||
protected View bindView(LayoutInflater inflater, ViewGroup container) { |
||||
binding = FragmentLocalSourceBinding.inflate(inflater, container, false); |
||||
return binding.getRoot(); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
Single.create((SingleOnSubscribe<List<BookSource>>) emitter -> emitter.onSuccess(BookSourceManager.getAllLocalSource())).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<List<BookSource>>() { |
||||
@Override |
||||
public void onSuccess(@NonNull List<BookSource> sources) { |
||||
mBookSources = sources; |
||||
initSourceList(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
} |
||||
}); |
||||
|
||||
} |
||||
|
||||
private void initSourceList() { |
||||
mAdapter = new LocalSourceAdapter(mBookSources); |
||||
mAdapter.setOnItemClickListener((view, pos) -> { |
||||
mAdapter.setCheckedItem(pos); |
||||
if (mAdapter.getCheckedCount() == mAdapter.getItemCount()) { |
||||
binding.tvSelectAll.setText(R.string.cancle_select_all); |
||||
} else { |
||||
binding.tvSelectAll.setText(R.string.select_all); |
||||
} |
||||
}); |
||||
binding.tvSelectAll.setOnClickListener(v -> { |
||||
if (mAdapter.getCheckedCount() != mAdapter.getItemCount()) { |
||||
mAdapter.setCheckedAll(true); |
||||
binding.tvSelectAll.setText(R.string.cancle_select_all); |
||||
} else { |
||||
mAdapter.setCheckedAll(false); |
||||
binding.tvSelectAll.setText(R.string.select_all); |
||||
} |
||||
}); |
||||
binding.tvEnableSelected.setOnClickListener(v -> changeSourcesStatus(true)); |
||||
binding.tvDisableSelected.setOnClickListener(v -> changeSourcesStatus(false)); |
||||
binding.tvCheckSelected.setOnClickListener(v -> ToastUtils.showInfo("校验功能即将上线")); |
||||
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); |
||||
binding.recyclerView.setAdapter(mAdapter); |
||||
//设置分割线
|
||||
binding.recyclerView.addItemDecoration(new DividerItemDecoration(getContext())); |
||||
mAdapter.refreshItems(mBookSources); |
||||
} |
||||
|
||||
private void changeSourcesStatus(boolean isEnable) { |
||||
List<BookSource> sources = mAdapter.getCheckedBookSources(); |
||||
if (sources.size() == 0) { |
||||
ToastUtils.showWarring("请选择书源"); |
||||
} else { |
||||
for (BookSource source : sources) { |
||||
source.setEnable(isEnable); |
||||
} |
||||
BookSourceManager.saveDatas(sources) |
||||
.subscribe(new MySingleObserver<Boolean>() { |
||||
@Override |
||||
public void onSuccess(@androidx.annotation.NonNull Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
mAdapter.notifyDataSetChanged(); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
public void startSearch(String newText) { |
||||
if (mAdapter != null) { |
||||
mAdapter.getFilter().filter(newText); |
||||
} |
||||
} |
||||
|
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue