1. Dependency check with download redirect support.

2. Config editor upgrade (ref: https://github.com/josdejong/jsoneditor).
3. Upgrade for Android R.
4. Bug fix
This commit is contained in:
ZaneYork 2020-10-16 17:12:10 +08:00
parent feca85bcbe
commit d014d66f0d
30 changed files with 1337 additions and 185 deletions

View File

@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="Zane">
<words>
<w>stadew</w>
</words>
</dictionary>
</component>

View File

@ -5,21 +5,18 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
compileSdkVersion 30
defaultConfig {
applicationId "com.zane.smapiinstaller"
minSdkVersion 19
targetSdkVersion 28
versionCode 56
versionName "1.6.3"
targetSdkVersion 30
versionCode 58
versionName "1.6.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a'
}
}
buildTypes {
@ -53,8 +50,8 @@ android {
}
}
viewBinding {
enabled = true
buildFeatures {
viewBinding true
}
}

View File

@ -5,10 +5,12 @@
package="com.zane.smapiinstaller">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:name=".MainApplication"
@ -21,6 +23,7 @@
android:configChanges="orientation|keyboard|screenSize"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme"
android:requestLegacyExternalStorage="true"
tools:ignore="UnusedAttribute">
<activity
android:name=".MainActivity"

View File

@ -0,0 +1,82 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<!-- when using the mode "code", it's important to specify charset utf-8 -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<link href="jsoneditor.min.css" rel="stylesheet" type="text/css">
<script src="jsoneditor.min.js"></script>
<style type="text/css">
@media screen and (min-width: 520px) {
.jsoneditor-search input {
width: 120px;
}
}
@media screen and (max-width: 490px) {
.jsoneditor-search input {
width: 90px;
}
}
@media screen and (max-width: 460px) {
.jsoneditor-search input {
width: 60px;
}
}
@media screen and (max-width: 430px) {
.jsoneditor-search input {
width: 30px;
}
#jsoneditor {
min-width: 390px;
}
}
@media screen and (max-width: 410px) {
.jsoneditor-search input {
width: 20px;
}
#jsoneditor {
min-width: 370px;
}
}
</style>
</head>
<body>
<div id="jsoneditor" style="width: 100%"></div>
<script>
webObject = { getText: function(){ return '{"Array":[1,2,3],"Boolean":true,"Null":null,"Number":123,"Object":{"a":"b","c":"d"},"String":"Hello World"}'; }, getMode: function(){ return 'tree';}, getLanguage: function(){ return 'zh-CN';}, isEditable: function(){ return true;}, getHeight: function(){ return 400;}, };
// create the editor
const container = document.getElementById("jsoneditor");
container.style.height = webObject.getHeight() + "px";
const options = {
mode: webObject.getMode(),
modes: webObject.getMode() == "text-plain" ? [] : ["code","form","tree","text","preview"],
language: webObject.getLanguage(),
enableTransform: false,
onCreateMenu: function (items, node) {
return items.filter(function (item) {
return item.className !== 'jsoneditor-extract';
})
}
};
options.onEditable = function() { return webObject.isEditable();};
const editor = new JSONEditor(container, options);
// set json
var initialJson;
if(webObject.getMode() == "text-plain") {
initialJson = webObject.getText();
}
else{
initialJson = JSON.parse(webObject.getText());
}
editor.setSchema(null);
editor.set(initialJson);
function getJson()
{
// get json
webObject.setText(JSON.stringfy(editor.get()))
}
</script>
</body>
</html>

View File

@ -0,0 +1,749 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="240"
height="144"
id="svg4136"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="jsoneditor-icons.svg">
<title
id="title6512">JSON Editor Icons</title>
<metadata
id="metadata4148">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>JSON Editor Icons</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4146" />
<sodipodi:namedview
pagecolor="#ff63ff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1026"
id="namedview4144"
showgrid="true"
inkscape:zoom="4"
inkscape:cx="13.229181"
inkscape:cy="119.82429"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4136"
showguides="false"
borderlayer="false"
inkscape:showpageshadow="true"
showborder="true">
<inkscape:grid
type="xygrid"
id="grid4640"
empspacing="24" />
</sodipodi:namedview>
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1"
height="16"
width="16"
y="4"
x="4" />
<rect
id="svg_1-7"
height="16"
width="16"
y="3.999995"
x="28.000006"
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
x="52.000004"
y="3.999995"
width="16"
height="16"
id="rect4165" />
<rect
id="rect4175"
height="16"
width="16"
y="3.9999852"
x="172.00002"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
id="rect4175-3"
height="16"
width="16"
y="3.999995"
x="196"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<g
id="g4299"
style="stroke:none">
<rect
x="7.0000048"
y="10.999998"
width="9.9999924"
height="1.9999986"
id="svg_1-1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
x="11.000005"
y="7.0000114"
width="1.9999955"
height="9.9999838"
id="svg_1-1-1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
</g>
<g
id="g4299-3"
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
style="stroke:none">
<rect
x="7.0000048"
y="10.999998"
width="9.9999924"
height="1.9999986"
id="svg_1-1-0"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
x="11.000005"
y="7.0000114"
width="1.9999955"
height="9.9999838"
id="svg_1-1-1-9"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
</g>
<rect
id="svg_1-7-5"
height="6.9999905"
width="6.9999909"
y="7.0000048"
x="55.000004"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="58"
y="10.00001"
width="6.9999909"
height="6.9999905"
id="rect4354" />
<rect
id="svg_1-7-5-7"
height="6.9999905"
width="6.9999909"
y="10.000005"
x="58.000004"
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
<g
id="g4378">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="10.999999"
width="7.9999909"
height="1.9999965"
id="svg_1-7-5-3" />
<rect
id="rect4374"
height="1.9999946"
width="11.999995"
y="7.0000005"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
id="rect4376"
height="1.9999995"
width="3.9999928"
y="14.999996"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
</g>
<g
transform="matrix(1,0,0,-1,-23.999995,23.999995)"
id="g4383">
<rect
id="rect4385"
height="1.9999965"
width="7.9999909"
y="10.999999"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="7.0000005"
width="11.999995"
height="1.9999946"
id="rect4387" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="14.999996"
width="3.9999928"
height="1.9999995"
id="rect4389" />
</g>
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-4"
width="16"
height="16"
x="76"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
id="path4351"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
id="path4351-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-25"
width="16"
height="16"
x="100"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
id="path2987"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
id="path2987-1"
inkscape:connector-curvature="0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-73"
width="16"
height="16"
x="124"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
id="path3780"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
id="path3782"
inkscape:connector-curvature="0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-35"
width="16"
height="16"
x="148"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
id="path5008-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
id="path5008-2-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<rect
id="svg_1-7-2"
height="1.9999961"
width="11.999996"
y="64"
x="54"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
id="svg_1-7-2-2"
height="2.9999905"
width="2.9999907"
y="52"
x="80.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="85.000008"
y="52"
width="2.9999907"
height="2.9999905"
id="rect4561" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="80.000008"
y="58"
width="2.9999907"
height="2.9999905"
id="rect4563" />
<rect
id="rect4565"
height="2.9999905"
width="2.9999907"
y="58"
x="85.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
id="rect4567"
height="2.9999905"
width="2.9999907"
y="64"
x="80.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="85.000008"
y="64"
width="2.9999907"
height="2.9999905"
id="rect4569" />
<circle
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4571"
cx="110.06081"
cy="57.939209"
r="4.7438836" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="116.64566"
y="-31.79752"
width="4.229713"
height="6.4053884"
id="rect4563-2"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
<path
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 125,56 138.77027,56.095 132,64 Z"
id="path4613"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4615"
d="M 149,64 162.77027,63.905 156,56 Z"
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="54"
y="53"
width="11.999996"
height="1.9999961"
id="rect4638" />
<rect
id="svg_1-7-2-24"
height="1.9999957"
width="12.99999"
y="-56"
x="53"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
transform="matrix(0,1,-1,0,0,0)" />
<rect
transform="matrix(0,1,-1,0,0,0)"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="53"
y="-66"
width="12.99999"
height="1.9999957"
id="rect4657" />
<rect
id="rect4659"
height="0.99999291"
width="11.999999"
y="57"
x="54"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="54"
y="88.000122"
width="11.999996"
height="1.9999961"
id="rect4661" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="80.000008"
y="76.000122"
width="2.9999907"
height="2.9999905"
id="rect4663" />
<rect
id="rect4665"
height="2.9999905"
width="2.9999907"
y="76.000122"
x="85.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
id="rect4667"
height="2.9999905"
width="2.9999907"
y="82.000122"
x="80.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="85.000008"
y="82.000122"
width="2.9999907"
height="2.9999905"
id="rect4669" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="80.000008"
y="88.000122"
width="2.9999907"
height="2.9999905"
id="rect4671" />
<rect
id="rect4673"
height="2.9999905"
width="2.9999907"
y="88.000122"
x="85.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<circle
r="4.7438836"
cy="81.939331"
cx="110.06081"
id="circle4675"
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
id="rect4677"
height="6.4053884"
width="4.229713"
y="-14.826816"
x="133.6163"
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4679"
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
id="path4681"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<rect
id="rect4683"
height="1.9999961"
width="11.999996"
y="77.000122"
x="54"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
transform="matrix(0,1,-1,0,0,0)"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="77.000122"
y="-56"
width="12.99999"
height="1.9999957"
id="rect4685" />
<rect
id="rect4687"
height="1.9999957"
width="12.99999"
y="-66"
x="77.000122"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
transform="matrix(0,1,-1,0,0,0)" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="54"
y="81.000122"
width="11.999999"
height="0.99999291"
id="rect4689" />
<rect
id="rect4761-1"
height="1.9999945"
width="15.99999"
y="101"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-0"
height="1.9999945"
width="15.99999"
y="105"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-7"
height="1.9999945"
width="9"
y="109"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1"
height="1.9999945"
width="12"
y="125"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4"
height="1.9999945"
width="10"
y="137"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4-4"
height="1.9999945"
width="10"
y="129"
x="82"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4-4-3"
height="1.9999945"
width="9"
y="133"
x="82"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
id="path4138" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
id="path4138-1" />
<path
inkscape:connector-curvature="0"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
id="path3055-0-77" />
<path
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9850574,108.015 14.0298856,-0.03"
id="path5244-5-0-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9849874,132.015 14.0298866,-0.03"
id="path5244-5-0-5-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
id="path4138-12" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
id="path4138-1-3" />
<path
id="path6191"
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
id="path6193" />
<path
id="path6195"
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
sodipodi:type="star"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4500"
sodipodi:sides="3"
sodipodi:cx="11.55581"
sodipodi:cy="60.073242"
sodipodi:r1="5.1116104"
sodipodi:r2="2.5558052"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
inkscape:transform-center-x="-1.2779026" />
<path
inkscape:transform-center-x="1.277902"
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="false"
sodipodi:arg2="1.0471976"
sodipodi:arg1="0"
sodipodi:r2="2.5558052"
sodipodi:r1="5.1116104"
sodipodi:cy="60.073242"
sodipodi:cx="-36.611614"
sodipodi:sides="3"
id="path4502"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="star"
transform="scale(-1,1)" />
<path
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="false"
sodipodi:arg2="1.0471976"
sodipodi:arg1="0"
sodipodi:r2="2.5558052"
sodipodi:r1="5.1116104"
sodipodi:cy="60.073212"
sodipodi:cx="11.55581"
sodipodi:sides="3"
id="path4504"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="star"
transform="matrix(0,1,-1,0,72.0074,71.7877)"
inkscape:transform-center-y="1.2779029" />
<path
inkscape:transform-center-y="-1.2779026"
transform="matrix(0,-1,-1,0,96,96)"
sodipodi:type="star"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4506"
sodipodi:sides="3"
sodipodi:cx="11.55581"
sodipodi:cy="60.073212"
sodipodi:r1="5.1116104"
sodipodi:r2="2.5558052"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4615-5"
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179,55 0,6 2,0 0,-6"
id="path4300"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179,62 0,2 2,0 0,-2"
id="path4300-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.8"
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
id="path4268"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccc" />
<rect
id="rect4175-3-5"
height="16"
width="16"
y="4"
x="220"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<path
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 234,6 0,2 -5,5 0,5 -2,0 0,-5 -5,-5 0,-2"
id="path3546"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<g
transform="matrix(1.3333328,0,0,-1.5999992,-139.9999,127.19999)"
id="g4383-6">
<rect
id="rect4385-2"
height="1.2499905"
width="5.9999924"
y="12.625005"
x="198.00002"
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="15.125007"
width="7.4999928"
height="1.2499949"
id="rect4387-9" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="7.6250024"
width="2.9999909"
height="1.2499905"
id="rect4389-1-0" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="10.125004"
width="4.4999919"
height="1.2499905"
id="rect4389-1-9" />
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 207.00001,16.375004 0,-5.625005 -2.25,0 3,-3.1250014 3,3.1250014 -2.25,0 0,5.625005 -1.5,0"
id="path4402"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 164,100 0,3 -6,6 0,7 -4,0 0,-7 -6,-6 0,-3"
id="path3546-2-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-3"
height="16"
width="16"
y="28"
x="4" />
<path
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0"
id="path4402-5-7"
d="m 15,41 0,-7 -4,0 0,3 -5,-4 5,-4 0,3 6,0 0,9"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,10 @@ import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -32,6 +34,7 @@ import com.zane.smapiinstaller.logic.GameLauncher;
import com.zane.smapiinstaller.logic.ModAssetsManager;
import com.zane.smapiinstaller.utils.ConfigUtils;
import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
import com.zane.smapiinstaller.utils.JsonCallback;
import com.zane.smapiinstaller.utils.JsonUtil;
import com.zane.smapiinstaller.utils.TranslateUtil;
@ -64,6 +67,27 @@ public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private void requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 先判断有没有权限
if (!Environment.isExternalStorageManager()) {
DialogUtils.showConfirmDialog(MainActivity.instance, R.string.confirm, R.string.request_all_files_access_permission, ((dialog, dialogAction) -> {
if (dialogAction == DialogAction.POSITIVE) {
ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION, (resultCode, data) -> {
if (!Environment.isExternalStorageManager()) {
this.finish();
} else {
requestPermissions();
}
});
startActivityForResult(new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION), ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION);
}
else {
this.finish();
}
}));
return;
}
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
@ -235,7 +259,7 @@ public class MainActivity extends AppCompatActivity {
DialogUtils.showInputDialog(binding.appBarMain.toolbar, R.string.input, R.string.input_mods_path, Constants.MOD_PATH, Constants.MOD_PATH, (dialog, input) -> {
if (StringUtils.isNoneBlank(input)) {
String pathString = input.toString();
File file = new File(Environment.getExternalStorageDirectory(), pathString);
File file = new File(FileUtils.getStadewValleyBasePath(), pathString);
if (file.exists() && file.isDirectory()) {
Constants.MOD_PATH = pathString;
config.setModsPath(pathString);

View File

@ -25,6 +25,11 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ModUpdateCheckRequestDto {
public ModUpdateCheckRequestDto(List<ModInfo> mods, SemanticVersion gameVersion) {
this.mods = mods;
this.gameVersion = gameVersion;
}
/**
* 待检查MOD列表
*/
@ -37,7 +42,6 @@ public class ModUpdateCheckRequestDto {
/**
* 游戏版本
*/
@NonNull
private SemanticVersion gameVersion;
/**
* 平台版本

View File

@ -2,7 +2,9 @@ package com.zane.smapiinstaller.dto;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Zane
@ -12,9 +14,22 @@ public class ModUpdateCheckResponseDto {
private String id;
private UpdateInfo suggestedUpdate;
private List<String> errors;
private Metadata metadata;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UpdateInfo {
private String version;
private String url;
}
@Data
public static class Metadata {
private Main main;
@Data
public static class Main {
private String version;
private String url;
}
}
}

View File

@ -0,0 +1,11 @@
package com.zane.smapiinstaller.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Tuple2<U, V> {
private U first;
private V second;
}

View File

@ -0,0 +1,49 @@
package com.zane.smapiinstaller.dto;
import android.webkit.JavascriptInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* @author Zane
*/
@AllArgsConstructor
public class WebViewObject {
private String text;
private String mode;
private String language;
private boolean editable;
private int height;
@JavascriptInterface
public String getText() {
return text;
}
@JavascriptInterface
public void setText(String text) {
this.text = text;
}
@JavascriptInterface
public boolean isEditable() {
return editable;
}
@JavascriptInterface
public int getHeight() {
return height;
}
@JavascriptInterface
public String getMode() {
return mode;
}
@JavascriptInterface
public String getLanguage() {
return language;
}
}

View File

@ -10,7 +10,8 @@ import java.util.function.BiConsumer;
* @author Zane
*/
public class ActivityResultHandler {
public static int REQUEST_CODE_APP_INSTALL = 1001;
public static final int REQUEST_CODE_APP_INSTALL = 1001;
public static final int REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION = 1002;
public static ConcurrentHashMap<Integer, BiConsumer<Integer, Intent>> listenerMap = new ConcurrentHashMap<>();

View File

@ -7,17 +7,14 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import com.android.apksig.ApkSigner;
import com.android.apksig.ApkVerifier;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Predicate;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Iterables;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.zane.smapiinstaller.BuildConfig;
import com.zane.smapiinstaller.MainActivity;
@ -25,6 +22,7 @@ import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.constant.ManifestPatchConstants;
import com.zane.smapiinstaller.dto.Tuple2;
import com.zane.smapiinstaller.entity.ApkFilesManifest;
import com.zane.smapiinstaller.entity.ManifestEntry;
import com.zane.smapiinstaller.utils.DialogUtils;
@ -47,18 +45,17 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.Deflater;
import androidx.core.content.FileProvider;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import pxb.android.axml.NodeVisitor;
/**
@ -108,46 +105,44 @@ public class ApkPatcher {
String sourceDir = packageInfo.applicationInfo.publicSourceDir;
File apkFile = new File(sourceDir);
File externalFilesDir = Environment.getExternalStorageDirectory();
if (externalFilesDir != null) {
File dest = new File(externalFilesDir.getAbsolutePath() + "/SMAPI Installer/");
if (!dest.exists()) {
if (!dest.mkdir()) {
errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath()));
return null;
}
String stadewValleyBasePath = FileUtils.getStadewValleyBasePath();
File dest = new File(stadewValleyBasePath + "/SMAPI Installer/");
if (!dest.exists()) {
if (!dest.mkdir()) {
errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath()));
return null;
}
File distFile = new File(dest, apkFile.getName());
if (advancedStage == 0) {
AtomicInteger count = new AtomicInteger();
ZipUtil.unpack(apkFile, new File(externalFilesDir.getAbsolutePath() + "/StardewValley/"), name -> {
if (name.startsWith("assets/")) {
int progress = count.incrementAndGet();
if (progress % 30 == 0) {
emitProgress(progress / 30);
}
return name.replaceFirst("assets/", "");
}
return null;
});
return distFile.getAbsolutePath();
} else if (advancedStage == 1) {
File contentFolder = new File(externalFilesDir.getAbsolutePath() + "/StardewValley/Content");
if (contentFolder.exists()) {
if (!contentFolder.isDirectory()) {
errorMessage.set(context.getString(R.string.error_directory_exists_with_same_filename, contentFolder.getAbsolutePath()));
return null;
}
} else {
extract(0);
}
ZipUtils.removeEntries(sourceDir, "assets/Content", distFile.getAbsolutePath(), (progress) -> emitProgress((int) (progress * 0.05)));
} else {
Files.copy(apkFile, distFile);
}
emitProgress(5);
return distFile.getAbsolutePath();
}
File distFile = new File(dest, apkFile.getName());
if (advancedStage == 0) {
AtomicInteger count = new AtomicInteger();
ZipUtil.unpack(apkFile, new File(stadewValleyBasePath + "/StardewValley/"), name -> {
if (name.startsWith("assets/")) {
int progress = count.incrementAndGet();
if (progress % 30 == 0) {
emitProgress(progress / 30);
}
return name.replaceFirst("assets/", "");
}
return null;
});
return distFile.getAbsolutePath();
} else if (advancedStage == 1) {
File contentFolder = new File(stadewValleyBasePath + "/StardewValley/Content");
if (contentFolder.exists()) {
if (!contentFolder.isDirectory()) {
errorMessage.set(context.getString(R.string.error_directory_exists_with_same_filename, contentFolder.getAbsolutePath()));
return null;
}
} else {
extract(0);
}
ZipUtils.removeEntries(sourceDir, "assets/Content", distFile.getAbsolutePath(), (progress) -> emitProgress((int) (progress * 0.05)));
} else {
Files.copy(apkFile, distFile);
}
emitProgress(5);
return distFile.getAbsolutePath();
} catch (PackageManager.NameNotFoundException ignored) {
} catch (IOException e) {
Log.e(TAG, "Extract error", e);
@ -250,9 +245,9 @@ public class ApkPatcher {
AtomicReference<String> packageName = new AtomicReference<>();
AtomicReference<String> versionName = new AtomicReference<>();
AtomicLong versionCode = new AtomicLong();
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> {
Function<ManifestTagVisitor.AttrArgs, List<ManifestTagVisitor.AttrArgs>> attrProcessLogic = (attr) -> {
if (attr == null) {
return true;
return null;
}
if (attr.type == NodeVisitor.TYPE_STRING) {
String strObj = (String) attr.obj;
@ -276,6 +271,7 @@ public class ApkPatcher {
attr.obj = Constants.PATCHED_APP_NAME;
}
}
// return Collections.singletonList(new ManifestTagVisitor.AttrArgs(attr.ns, "requestLegacyExternalStorage", -1, NodeVisitor.TYPE_INT_BOOLEAN, true));
break;
case "authorities":
if (strObj.contains(packageName.get())) {
@ -296,10 +292,22 @@ public class ApkPatcher {
versionCode.set((int) attr.obj);
}
}
return true;
return null;
};
AtomicReference<Boolean> permissionAppended = new AtomicReference<>(true);
Function<ManifestTagVisitor.ChildArgs, List<ManifestTagVisitor.ChildArgs>> childProcessLogic = (child -> {
if (!permissionAppended.get() && StringUtils.equals(child.name, "uses-permission")) {
permissionAppended.set(true);
return Collections.singletonList(new ManifestTagVisitor.ChildArgs(
child.ns, child.name, Collections.singletonList(
new ManifestTagVisitor.AttrArgs(
"http://schemas.android.com/apk/res/android", "name", -1,
NodeVisitor.TYPE_STRING, "android.permission.MANAGE_EXTERNAL_STORAGE"))));
}
return null;
});
try {
byte[] modifyManifest = CommonLogic.modifyManifest(bytes, processLogic);
byte[] modifyManifest = CommonLogic.modifyManifest(bytes, attrProcessLogic, childProcessLogic);
if (StringUtils.endsWith(versionName.get(), ManifestPatchConstants.PATTERN_VERSION_AMAZON)) {
packageName.set(ManifestPatchConstants.APP_PACKAGE_NAME + ManifestPatchConstants.PATTERN_VERSION_AMAZON);
}
@ -335,56 +343,54 @@ public class ApkPatcher {
*/
public String sign(String apkPath) {
try {
File externalFilesDir = Environment.getExternalStorageDirectory();
String stadewValleyBasePath = FileUtils.getStadewValleyBasePath();
emitProgress(47);
if (externalFilesDir != null) {
String signApkPath = externalFilesDir.getAbsolutePath() + "/SMAPI Installer/base_signed.apk";
KeyStore ks = new KeyStoreFileManager.JksKeyStore();
try (InputStream fis = context.getAssets().open("debug.keystore.dat")) {
ks.load(fis, PASSWORD.toCharArray());
}
String alias = ks.aliases().nextElement();
X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias);
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder("debug", privateKey, Collections.singletonList(publicKey)).build();
emitProgress(49);
File outputFile = new File(signApkPath);
ApkSigner signer = new ApkSigner.Builder(Collections.singletonList(signerConfig))
.setInputApk(new File(apkPath))
.setOutputApk(outputFile)
.setV1SigningEnabled(true)
.setV2SigningEnabled(true).build();
long zipOpElapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
stopwatch.reset();
Thread thread = new Thread(() -> {
stopwatch.start();
while (true) {
try {
Thread.sleep(20);
long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
double progress = elapsed * 0.98 / zipOpElapsed;
if (progress < 1.0) {
emitProgress((int) (49 + 45 * progress));
}
} catch (InterruptedException ignored) {
return;
}
}
});
thread.start();
signer.sign();
FileUtils.forceDelete(new File(apkPath));
ApkVerifier.Result result = new ApkVerifier.Builder(outputFile).build().verify();
if (thread.isAlive() && !thread.isInterrupted()) {
thread.interrupt();
}
if (result.containsErrors()) {
errorMessage.set(result.getErrors().stream().map(ApkVerifier.IssueWithParams::toString).collect(Collectors.joining(",")));
return null;
}
emitProgress(95);
return signApkPath;
String signApkPath = stadewValleyBasePath + "/SMAPI Installer/base_signed.apk";
KeyStore ks = new KeyStoreFileManager.JksKeyStore();
try (InputStream fis = context.getAssets().open("debug.keystore.dat")) {
ks.load(fis, PASSWORD.toCharArray());
}
String alias = ks.aliases().nextElement();
X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias);
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray());
ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder("debug", privateKey, Collections.singletonList(publicKey)).build();
emitProgress(49);
File outputFile = new File(signApkPath);
ApkSigner signer = new ApkSigner.Builder(Collections.singletonList(signerConfig))
.setInputApk(new File(apkPath))
.setOutputApk(outputFile)
.setV1SigningEnabled(true)
.setV2SigningEnabled(true).build();
long zipOpElapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
stopwatch.reset();
Thread thread = new Thread(() -> {
stopwatch.start();
while (true) {
try {
Thread.sleep(20);
long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
double progress = elapsed * 0.98 / zipOpElapsed;
if (progress < 1.0) {
emitProgress((int) (49 + 45 * progress));
}
} catch (InterruptedException ignored) {
return;
}
}
});
thread.start();
signer.sign();
FileUtils.forceDelete(new File(apkPath));
ApkVerifier.Result result = new ApkVerifier.Builder(outputFile).build().verify();
if (thread.isAlive() && !thread.isInterrupted()) {
thread.interrupt();
}
if (result.containsErrors()) {
errorMessage.set(result.getErrors().stream().map(ApkVerifier.IssueWithParams::toString).collect(Collectors.joining(",")));
return null;
}
emitProgress(95);
return signApkPath;
} catch (Exception e) {
Log.e(TAG, "Sign error", e);
errorMessage.set(e.getLocalizedMessage());

View File

@ -11,7 +11,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
@ -20,7 +19,6 @@ import android.widget.ImageView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.lmntrx.android.library.livin.missme.ProgressDialog;
@ -28,6 +26,7 @@ import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.MainApplication;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.dto.Tuple2;
import com.zane.smapiinstaller.entity.ApkFilesManifest;
import com.zane.smapiinstaller.entity.ManifestEntry;
import com.zane.smapiinstaller.utils.DialogUtils;
@ -43,9 +42,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor;
import pxb.android.axml.AxmlWriter;
@ -196,7 +196,7 @@ public class CommonLogic {
if (manifestEntries == null) {
return false;
}
File basePath = new File(Environment.getExternalStorageDirectory() + "/StardewValley/");
File basePath = new File(FileUtils.getStadewValleyBasePath() + "/StardewValley/");
if (!basePath.exists()) {
if (!basePath.mkdir()) {
return false;
@ -244,18 +244,18 @@ public class CommonLogic {
* 修改AndroidManifest.xml文件
*
* @param bytes AndroidManifest.xml文件字符数组
* @param processLogic 处理逻辑
* @param attrProcessLogic 处理逻辑
* @return 修改后的AndroidManifest.xml文件字符数组
* @throws IOException 异常
*/
public static byte[] modifyManifest(byte[] bytes, Predicate<ManifestTagVisitor.AttrArgs> processLogic) throws IOException {
public static byte[] modifyManifest(byte[] bytes, Function<ManifestTagVisitor.AttrArgs, List<ManifestTagVisitor.AttrArgs>> attrProcessLogic, Function<ManifestTagVisitor.ChildArgs, List<ManifestTagVisitor.ChildArgs>> childProcessLogic) throws IOException {
AxmlReader reader = new AxmlReader(bytes);
AxmlWriter writer = new AxmlWriter();
reader.accept(new AxmlVisitor(writer) {
@Override
public NodeVisitor child(String ns, String name) {
NodeVisitor child = super.child(ns, name);
return new ManifestTagVisitor(child, processLogic);
return new ManifestTagVisitor(child, attrProcessLogic, childProcessLogic);
}
});
return writer.toByteArray();

View File

@ -1,7 +1,5 @@
package com.zane.smapiinstaller.logic;
import android.os.Environment;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.entity.FrameworkConfig;
import com.zane.smapiinstaller.utils.FileUtils;
@ -16,7 +14,7 @@ public class ConfigManager {
private FrameworkConfig config;
public ConfigManager() {
File configFile = new File(Environment.getExternalStorageDirectory(), Constants.CONFIG_PATH);
File configFile = new File(FileUtils.getStadewValleyBasePath(), Constants.CONFIG_PATH);
if(configFile.exists()) {
config = FileUtils.getFileJson(configFile, FrameworkConfig.class);
}
@ -31,7 +29,7 @@ public class ConfigManager {
}
public void flushConfig() {
File configFile = new File(Environment.getExternalStorageDirectory(), Constants.CONFIG_PATH);
File configFile = new File(FileUtils.getStadewValleyBasePath(), Constants.CONFIG_PATH);
FileUtils.writeFileJson(configFile, config);
}
}

View File

@ -1,7 +1,10 @@
package com.zane.smapiinstaller.logic;
import com.google.common.base.Predicate;
import java.util.List;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import pxb.android.axml.NodeVisitor;
/**
@ -9,38 +12,56 @@ import pxb.android.axml.NodeVisitor;
*/
class ManifestTagVisitor extends NodeVisitor {
private final Predicate<AttrArgs> attrProcessLogic;
private final Function<AttrArgs, List<AttrArgs>> attrProcessLogic;
private final Function<ChildArgs, List<ChildArgs>> childProcessLogic;
public ManifestTagVisitor(NodeVisitor nv, Predicate<AttrArgs> attrProcessLogic) {
public ManifestTagVisitor(NodeVisitor nv, Function<AttrArgs, List<AttrArgs>> attrProcessLogic, Function<ChildArgs, List<ChildArgs>> childProcessLogic) {
super(nv);
this.attrProcessLogic = attrProcessLogic;
this.childProcessLogic = childProcessLogic;
}
@Override
public void attr(String ns, String name, int resourceId, int type, Object obj) {
AttrArgs attrArgs = new AttrArgs(ns, name, resourceId, type, obj);
attrProcessLogic.apply(attrArgs);
List<AttrArgs> appendAttrs = attrProcessLogic.apply(attrArgs);
super.attr(attrArgs.ns, attrArgs.name, attrArgs.resourceId, attrArgs.type, attrArgs.obj);
if(appendAttrs != null) {
for (AttrArgs attr: appendAttrs) {
super.attr(attr.ns, attr.name, attr.resourceId, attr.type, attr.obj);
}
}
}
@Override
public NodeVisitor child(String ns, String name) {
return new ManifestTagVisitor(super.child(ns, name), attrProcessLogic);
ChildArgs childArgs = new ChildArgs(ns, name, null);
List<ChildArgs> appendChild = childProcessLogic.apply(childArgs);
NodeVisitor child = super.child(childArgs.ns, childArgs.name);
if(appendChild != null) {
for (ChildArgs c: appendChild) {
NodeVisitor visitor = super.child(c.ns, c.name);
if(c.attrArgs != null) {
for (AttrArgs attr: c.attrArgs) {
visitor.attr(attr.ns, attr.name, attr.resourceId, attr.type, attr.obj);
}
}
}
}
return new ManifestTagVisitor(child, attrProcessLogic, childProcessLogic);
}
@AllArgsConstructor
public static class ChildArgs {
String ns;
String name;
List<AttrArgs> attrArgs;
}
@AllArgsConstructor
public static class AttrArgs {
String ns;
String name;
int resourceId;
int type;
Object obj;
AttrArgs(String ns, String name, int resourceId, int type, Object obj) {
this.ns = ns;
this.name = name;
this.resourceId = resourceId;
this.type = type;
this.obj = obj;
}
}
}

View File

@ -2,7 +2,6 @@ package com.zane.smapiinstaller.logic;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@ -17,16 +16,18 @@ import com.google.common.collect.Queues;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.model.Response;
import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.MobileNavigationDirections;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.dto.ModUpdateCheckRequestDto;
import com.zane.smapiinstaller.dto.ModUpdateCheckResponseDto;
import com.zane.smapiinstaller.dto.Tuple2;
import com.zane.smapiinstaller.entity.ModManifestEntry;
import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
import com.zane.smapiinstaller.utils.JsonUtil;
import com.zane.smapiinstaller.utils.JsonCallback;
import com.zane.smapiinstaller.utils.JsonUtil;
import com.zane.smapiinstaller.utils.VersionUtil;
import org.apache.commons.lang3.StringUtils;
@ -35,17 +36,22 @@ import org.zeroturnaround.zip.ZipUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
/**
* Mod资源管理器
*
* @author Zane
*/
public class ModAssetsManager {
@ -68,7 +74,7 @@ public class ModAssetsManager {
*/
public static ModManifestEntry findFirstModIf(Predicate<ModManifestEntry> filter) {
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH));
files.add(new File(FileUtils.getStadewValleyBasePath(), Constants.MOD_PATH));
do {
File currentFile = files.poll();
if (currentFile != null && currentFile.exists()) {
@ -115,7 +121,7 @@ public class ModAssetsManager {
*/
public static List<ModManifestEntry> findAllInstalledMods(boolean ignoreDisabledMod) {
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH));
files.add(new File(FileUtils.getStadewValleyBasePath(), Constants.MOD_PATH));
List<ModManifestEntry> mods = new ArrayList<>(30);
do {
File currentFile = files.poll();
@ -162,7 +168,7 @@ public class ModAssetsManager {
if (modManifestEntries == null) {
return false;
}
File modFolder = new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH);
File modFolder = new File(FileUtils.getStadewValleyBasePath(), Constants.MOD_PATH);
ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
for (ModManifestEntry mod : modManifestEntries) {
if (installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) {
@ -253,18 +259,28 @@ public class ModAssetsManager {
* @param returnCallback 回调函数
*/
private void checkUnsatisfiedDependencies(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> returnCallback) {
List<String> dependencyErrors = installedModMap.values().stream()
List<Tuple2<String, List<String>>> dependencyErrors = installedModMap.values().stream()
.map(mod -> checkModDependencyError(mod, installedModMap))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (dependencyErrors.size() > 0) {
DialogUtils.showConfirmDialog(root, R.string.error,
Joiner.on(";").join(dependencyErrors),
R.string.continue_text, R.string.abort,
dependencyErrors.stream().map(Tuple2::getFirst).collect(Collectors.joining(";")),
R.string.continue_text, R.string.menu_download,
((dialog, which) -> {
if (which == DialogAction.POSITIVE) {
returnCallback.accept(true);
} else {
List<ModUpdateCheckRequestDto.ModInfo> list = dependencyErrors.stream()
.map(Tuple2::getSecond).flatMap(Collection::stream).distinct()
.map(item -> {
ModUpdateCheckRequestDto.ModInfo modInfo = new ModUpdateCheckRequestDto.ModInfo();
modInfo.setId(item);
return modInfo;
})
.distinct()
.collect(Collectors.toList());
redirectModDownload(list);
returnCallback.accept(false);
}
}));
@ -280,17 +296,26 @@ public class ModAssetsManager {
* @param returnCallback 回调函数
*/
private void checkContentpacks(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> returnCallback) {
List<String> dependencyErrors = installedModMap.values().stream()
List<Tuple2<String, String>> dependencyErrors = installedModMap.values().stream()
.map(mod -> checkContentPackDependencyError(mod, installedModMap))
.filter(Objects::nonNull).collect(Collectors.toList());
if (!dependencyErrors.isEmpty()) {
DialogUtils.showConfirmDialog(root, R.string.error,
Joiner.on(";").join(dependencyErrors),
R.string.continue_text, R.string.abort,
dependencyErrors.stream().map(Tuple2::getFirst).collect(Collectors.joining(";")),
R.string.continue_text, R.string.menu_download,
((dialog, which) -> {
if (which == DialogAction.POSITIVE) {
returnCallback.accept(true);
} else {
List<ModUpdateCheckRequestDto.ModInfo> list = dependencyErrors.stream()
.map(item -> {
ModUpdateCheckRequestDto.ModInfo modInfo = new ModUpdateCheckRequestDto.ModInfo();
modInfo.setId(item.getSecond());
return modInfo;
})
.distinct()
.collect(Collectors.toList());
redirectModDownload(list);
returnCallback.accept(false);
}
}));
@ -299,6 +324,45 @@ public class ModAssetsManager {
}
}
private void redirectModDownload(List<ModUpdateCheckRequestDto.ModInfo> list) {
ModUpdateCheckRequestDto requestDto = new ModUpdateCheckRequestDto(list);
try {
requestDto.setIncludeExtendedMetadata(true);
OkGo.<List<ModUpdateCheckResponseDto>>post(Constants.UPDATE_CHECK_SERVICE_URL)
.upJson(JsonUtil.toJson(requestDto))
.execute(new JsonCallback<List<ModUpdateCheckResponseDto>>(new TypeReference<List<ModUpdateCheckResponseDto>>() {
}) {
@Override
public void onError(Response<List<ModUpdateCheckResponseDto>> response) {
super.onError(response);
}
@Override
public void onSuccess(Response<List<ModUpdateCheckResponseDto>> response) {
List<ModUpdateCheckResponseDto> checkResponseDtos = response.body();
if (checkResponseDtos != null) {
List<ModUpdateCheckResponseDto> list = checkResponseDtos.stream()
.filter(dto -> dto.getMetadata() != null && dto.getMetadata().getMain() != null && StringUtils.isNoneBlank(dto.getMetadata().getMain().getUrl()))
.collect(Collectors.toList());
try {
if (list.size() > 0) {
for (ModUpdateCheckResponseDto dto : list) {
dto.setSuggestedUpdate(new ModUpdateCheckResponseDto.UpdateInfo(dto.getMetadata().getMain().getVersion(), dto.getMetadata().getMain().getUrl()));
}
NavController controller = Navigation.findNavController(CommonLogic.getActivityFromView(root), R.id.nav_host_fragment);
controller.navigate(MobileNavigationDirections.actionNavAnyToModUpdateFragment(JsonUtil.toJson(list)));
}
} catch (Exception e) {
Crashes.trackError(e);
}
}
}
});
} catch (Exception e) {
Crashes.trackError(e);
}
}
public void checkModUpdate(Consumer<List<ModUpdateCheckResponseDto>> callback) {
if (checkUpdating.get()) {
return;
@ -344,13 +408,15 @@ public class ModAssetsManager {
}
}
private String checkModDependencyError(ModManifestEntry mod, ImmutableListMultimap<String, ModManifestEntry> installedModMap) {
private Tuple2<String, List<String>> checkModDependencyError(ModManifestEntry mod, ImmutableListMultimap<String, ModManifestEntry> installedModMap) {
if (mod.getDependencies() != null) {
List<ModManifestEntry> unsatisfiedDependencies = mod.getDependencies().stream()
.filter(dependency -> isDependencyIsExist(dependency, installedModMap))
.collect(Collectors.toList());
if (unsatisfiedDependencies.size() > 0) {
return root.getContext().getString(R.string.error_depends_on_mod, mod.getUniqueID(), Joiner.on(",").join(Lists.transform(unsatisfiedDependencies, ModManifestEntry::getUniqueID)));
return new Tuple2<>(
root.getContext().getString(R.string.error_depends_on_mod, mod.getUniqueID(), Joiner.on(",").join(Lists.transform(unsatisfiedDependencies, ModManifestEntry::getUniqueID))),
unsatisfiedDependencies.stream().map(ModManifestEntry::getUniqueID).collect(Collectors.toList()));
}
}
return null;
@ -386,7 +452,7 @@ public class ModAssetsManager {
return false;
}
private String checkContentPackDependencyError(ModManifestEntry mod, ImmutableListMultimap<String, ModManifestEntry> installedModMap) {
private Tuple2<String, String> checkContentPackDependencyError(ModManifestEntry mod, ImmutableListMultimap<String, ModManifestEntry> installedModMap) {
ModManifestEntry dependency = mod.getContentPackFor();
if (dependency != null) {
if (dependency.getIsRequired() != null && !dependency.getIsRequired()) {
@ -403,7 +469,7 @@ public class ModAssetsManager {
}
}
if (entries.size() != 1) {
return root.getContext().getString(R.string.error_depends_on_mod, mod.getUniqueID(), dependency.getUniqueID());
return new Tuple2<>(root.getContext().getString(R.string.error_depends_on_mod, mod.getUniqueID(), dependency.getUniqueID()), dependency.getUniqueID());
}
String version = entries.get(0).getVersion();
if (!StringUtils.isBlank(version)) {
@ -411,7 +477,7 @@ public class ModAssetsManager {
return null;
}
if (VersionUtil.compareVersion(version, dependency.getMinimumVersion()) < 0) {
return root.getContext().getString(R.string.error_depends_on_mod_version, mod.getUniqueID(), dependency.getUniqueID(), dependency.getMinimumVersion());
return new Tuple2<>(root.getContext().getString(R.string.error_depends_on_mod_version, mod.getUniqueID(), dependency.getUniqueID(), dependency.getMinimumVersion()), dependency.getUniqueID());
}
}
return null;

View File

@ -7,12 +7,19 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.hjq.language.LanguagesManager;
import com.zane.smapiinstaller.BuildConfig;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.databinding.FragmentConfigEditBinding;
import com.zane.smapiinstaller.dto.WebViewObject;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
@ -21,6 +28,7 @@ import com.zane.smapiinstaller.utils.JsonUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
@ -35,30 +43,72 @@ public class ConfigEditFragment extends Fragment {
private String configPath;
private FragmentConfigEditBinding binding;
private WebViewObject webObject = null;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentConfigEditBinding.inflate(inflater, container, false);
initView();
binding.buttonConfigCancel.setOnClickListener(v -> onConfigCancel());
binding.buttonConfigSave.setOnClickListener(v -> onConfigSave());
binding.buttonLogParser.setOnClickListener(v -> onLogParser());
return binding.getRoot();
}
private void initView() {
CommonLogic.doOnNonNull(this.getArguments(), arguments -> {
ConfigEditFragmentArgs args = ConfigEditFragmentArgs.fromBundle(arguments);
editable = args.getEditable();
if (!editable) {
binding.editTextConfigEdit.setKeyListener(null);
binding.buttonConfigSave.setVisibility(View.INVISIBLE);
binding.buttonConfigCancel.setVisibility(View.INVISIBLE);
binding.buttonLogParser.setVisibility(View.VISIBLE);
}
binding.editTextConfigWebview.getSettings().setJavaScriptEnabled(true);
binding.editTextConfigWebview.setWebChromeClient(new WebChromeClient());
binding.editTextConfigWebview.setWebViewClient(new WebViewClient());
configPath = args.getConfigPath();
File file = new File(configPath);
if (file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) {
String fileText = FileUtils.getFileText(file);
if (fileText != null) {
binding.editTextConfigEdit.setText(fileText);
binding.scrollView.post(() -> {
CommonLogic.doOnNonNull(this.getContext(), (context -> {
int height = (int) (binding.scrollView.getMeasuredHeight() / context.getResources().getDisplayMetrics().density * 0.95);
String lang = LanguagesManager.getAppLanguage(context).getCountry();
switch (lang){
case "zh":
lang = "zh-CN";
break;
case "fr":
lang = "fr-FR";
break;
case "pt":
lang = "pt-BR";
break;
default:
break;
}
if (editable) {
webObject = new WebViewObject(fileText, "code", lang, true, height);
binding.editTextConfigWebview.addJavascriptInterface(webObject, "webObject");
} else {
webObject = new WebViewObject(fileText, "text-plain", lang, false, height);
binding.editTextConfigWebview.addJavascriptInterface(webObject, "webObject");
}
String assetText = FileUtils.getAssetText(context, "jsoneditor/editor.html");
if (assetText != null) {
binding.editTextConfigWebview.loadDataWithBaseURL(
"file:///android_asset/jsoneditor/",
assetText,
"text/html",
"utf-8", "");
}
}));
});
}
} else {
binding.editTextConfigEdit.setText("");
binding.editTextConfigEdit.setKeyListener(null);
DialogUtils.showConfirmDialog(binding.getRoot(), R.string.error, this.getString(R.string.text_too_large), R.string.open_with, R.string.cancel, ((dialog, which) -> {
if (which == DialogAction.POSITIVE) {
Intent intent = new Intent("android.intent.action.VIEW");
@ -79,9 +129,6 @@ public class ConfigEditFragment extends Fragment {
}));
}
});
binding.buttonConfigCancel.setOnClickListener(v -> onConfigCancel());
binding.buttonConfigSave.setOnClickListener(v -> onConfigSave());
return binding.getRoot();
}
@Override
@ -92,11 +139,14 @@ public class ConfigEditFragment extends Fragment {
private void onConfigSave() {
try {
JsonUtil.checkJson(binding.editTextConfigEdit.getText().toString());
FileOutputStream outputStream = new FileOutputStream(configPath);
try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) {
outputStreamWriter.write(binding.editTextConfigEdit.getText().toString());
outputStreamWriter.flush();
if(webObject != null) {
binding.editTextConfigWebview.loadUrl("javascript:getJson()");
JsonUtil.checkJson(webObject.getText());
FileOutputStream outputStream = new FileOutputStream(configPath);
try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) {
outputStreamWriter.write(webObject.getText());
outputStreamWriter.flush();
}
}
} catch (Exception e) {
DialogUtils.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage());

View File

@ -39,8 +39,13 @@ public class ConfigFragment extends Fragment {
return false;
});
binding.viewModList.addItemDecoration(new DividerItemDecoration(binding.viewModList.getContext(), DividerItemDecoration.VERTICAL));
binding.buttonSearch.addTextChangedListener((TextChangedWatcher) (s, start, before, count) -> configViewModel.filter(s));
binding.buttonSortBy.setOnClickListener(v -> onSortByClick());
binding.buttonSearch.addTextChangedListener(new TextChangedWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
configViewModel.filter(s);
}
});
binding.buttonSortBy.setOnClickListener(this::onSortByClick);
return binding.getRoot();
}
@ -50,7 +55,7 @@ public class ConfigFragment extends Fragment {
binding = null;
}
void onSortByClick() {
public void onSortByClick(View v) {
int index = 0;
switch (configViewModel.getSortBy()) {
case "Name asc":

View File

@ -1,7 +1,6 @@
package com.zane.smapiinstaller.ui.help;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -12,6 +11,7 @@ import com.zane.smapiinstaller.databinding.FragmentHelpBinding;
import com.zane.smapiinstaller.entity.HelpItemList;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.logic.UpdatableListManager;
import com.zane.smapiinstaller.utils.FileUtils;
import java.io.File;
@ -65,7 +65,7 @@ public class HelpFragment extends Fragment {
private void showLog() {
CommonLogic.doOnNonNull(this.getView(), view -> {
NavController controller = Navigation.findNavController(view);
File logFile = new File(Environment.getExternalStorageDirectory(), Constants.LOG_PATH);
File logFile = new File(FileUtils.getStadewValleyBasePath(), Constants.LOG_PATH);
if (logFile.exists()) {
MobileNavigationDirections.ActionNavAnyToConfigEditFragment action = HelpFragmentDirections.actionNavAnyToConfigEditFragment(logFile.getAbsolutePath());
action.setEditable(false);

View File

@ -3,7 +3,6 @@ package com.zane.smapiinstaller.ui.install;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -21,6 +20,7 @@ import com.zane.smapiinstaller.logic.ModAssetsManager;
import com.zane.smapiinstaller.ui.main.MainTabsFragmentDirections;
import com.zane.smapiinstaller.utils.ConfigUtils;
import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
@ -55,7 +55,7 @@ public class InstallFragment extends Fragment {
binding.layoutAdvInstall.setVisibility(View.VISIBLE);
}
try {
String firstLine = Files.asCharSource(new File(Environment.getExternalStorageDirectory(), Constants.LOG_PATH), StandardCharsets.UTF_8).readFirstLine();
String firstLine = Files.asCharSource(new File(FileUtils.getStadewValleyBasePath(), Constants.LOG_PATH), StandardCharsets.UTF_8).readFirstLine();
if (StringUtils.isNoneBlank(firstLine)) {
String versionString = RegExUtils.removePattern(firstLine, "\\[.+\\]\\s+");
versionString = RegExUtils.removePattern(versionString, "\\s+with.+");

View File

@ -70,6 +70,13 @@ public class ModUpdateAdapter extends RecyclerView.Adapter<ModUpdateAdapter.View
activity.getString(R.string.mod_version_update_text, modManifestEntry.getVersion(), updateInfo.getSuggestedUpdate().getVersion())
));
}
else {
binding.textViewModName.setText(this.updateInfo.getId());
CommonLogic.doOnNonNull(CommonLogic.getActivityFromView(binding.textViewModName),
activity -> binding.textViewModVersion.setText(
activity.getString(R.string.mod_version_update_text, updateInfo.getSuggestedUpdate().getVersion(), updateInfo.getSuggestedUpdate().getVersion())
));
}
}
public ViewHolder(View view) {

View File

@ -279,7 +279,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
* @return 移除前缀后的路径
*/
public static String toPrettyPath(String path) {
return StringUtils.removeStart(path, Environment.getExternalStorageDirectory().getAbsolutePath());
return StringUtils.removeStart(path, FileUtils.getStadewValleyBasePath());
}
/**
@ -310,4 +310,8 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
}
return null;
}
public static String getStadewValleyBasePath() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
}

View File

@ -5,8 +5,7 @@ import android.text.Editable;
/**
* @author Zane
*/
@FunctionalInterface
public interface TextChangedWatcher extends android.text.TextWatcher {
public abstract class TextChangedWatcher implements android.text.TextWatcher {
/**
* Do nothing
* @param s origin string
@ -15,7 +14,7 @@ public interface TextChangedWatcher extends android.text.TextWatcher {
* @param after modified string
*/
@Override
default void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
/**
* Text changed event
@ -24,12 +23,12 @@ public interface TextChangedWatcher extends android.text.TextWatcher {
* @param count modify count
*/
@Override
void onTextChanged(CharSequence s, int start, int before, int count);
public abstract void onTextChanged(CharSequence s, int start, int before, int count);
/**
* Do nothing
* @param s target view
*/
@Override
default void afterTextChanged(Editable s) {}
public void afterTextChanged(Editable s) {}
}

View File

@ -4,6 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
@ -11,12 +12,10 @@
app:layout_constraintEnd_toStartOf="@id/guideline_v2"
app:layout_constraintTop_toBottomOf="@id/guideline_h1"
app:layout_constraintBottom_toTopOf="@id/guideline_h2">
<EditText
android:id="@+id/edit_text_config_edit"
<WebView
android:id="@+id/edit_text_config_webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:gravity="top|start"/>
android:layout_height="wrap_content"/>
</ScrollView>
<Button

View File

@ -84,4 +84,5 @@
<string name="request_unknown_source_permission">需要开启未知源权限以安装Mod框架是否前往设置开启</string>
<string name="settings_rewrite_missing">重寫增強</string>
<string name="settings_set_app_name">設定應用名稱</string>
<string name="request_all_files_access_permission">安卓11及以後版本需要授權文件管理權限以完成框架安裝是否繼續</string>
</resources>

View File

@ -84,4 +84,5 @@
<string name="request_unknown_source_permission">需要开启未知源权限以安装Mod框架是否前往设置开启</string>
<string name="settings_rewrite_missing">重写增强</string>
<string name="settings_set_app_name">设置应用名称</string>
<string name="request_all_files_access_permission">安卓11及以后版本需要授权文件管理权限以完成框架安装是否继续</string>
</resources>

View File

@ -88,4 +88,5 @@
<string name="request_unknown_source_permission">Needs to switch unknown source permission on to install mod framework, open settings?</string>
<string name="settings_rewrite_missing">Rewrite Missing</string>
<string name="settings_set_app_name">Patched App Name</string>
<string name="request_all_files_access_permission">Need all files access permission on Android Q or above to install mod framework, continue?</string>
</resources>

View File

@ -8,7 +8,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:4.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'