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

View File

@ -5,10 +5,12 @@
package="com.zane.smapiinstaller"> package="com.zane.smapiinstaller">
<uses-permission android:name="android.permission.INTERNET"/> <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_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_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.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 <application
android:name=".MainApplication" android:name=".MainApplication"
@ -21,6 +23,7 @@
android:configChanges="orientation|keyboard|screenSize" android:configChanges="orientation|keyboard|screenSize"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:requestLegacyExternalStorage="true"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<activity <activity
android:name=".MainActivity" 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.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.provider.Settings;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -32,6 +34,7 @@ import com.zane.smapiinstaller.logic.GameLauncher;
import com.zane.smapiinstaller.logic.ModAssetsManager; import com.zane.smapiinstaller.logic.ModAssetsManager;
import com.zane.smapiinstaller.utils.ConfigUtils; import com.zane.smapiinstaller.utils.ConfigUtils;
import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
import com.zane.smapiinstaller.utils.JsonCallback; import com.zane.smapiinstaller.utils.JsonCallback;
import com.zane.smapiinstaller.utils.JsonUtil; import com.zane.smapiinstaller.utils.JsonUtil;
import com.zane.smapiinstaller.utils.TranslateUtil; import com.zane.smapiinstaller.utils.TranslateUtil;
@ -64,6 +67,27 @@ public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding; private ActivityMainBinding binding;
private void requestPermissions() { 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) if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, 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) -> { 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)) { if (StringUtils.isNoneBlank(input)) {
String pathString = input.toString(); String pathString = input.toString();
File file = new File(Environment.getExternalStorageDirectory(), pathString); File file = new File(FileUtils.getStadewValleyBasePath(), pathString);
if (file.exists() && file.isDirectory()) { if (file.exists() && file.isDirectory()) {
Constants.MOD_PATH = pathString; Constants.MOD_PATH = pathString;
config.setModsPath(pathString); config.setModsPath(pathString);

View File

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

View File

@ -2,7 +2,9 @@ package com.zane.smapiinstaller.dto;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
/** /**
* @author Zane * @author Zane
@ -12,9 +14,22 @@ public class ModUpdateCheckResponseDto {
private String id; private String id;
private UpdateInfo suggestedUpdate; private UpdateInfo suggestedUpdate;
private List<String> errors; private List<String> errors;
private Metadata metadata;
@Data @Data
@NoArgsConstructor
@AllArgsConstructor
public static class UpdateInfo { public static class UpdateInfo {
private String version; private String version;
private String url; 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 * @author Zane
*/ */
public class ActivityResultHandler { 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<>(); 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.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import com.android.apksig.ApkSigner; import com.android.apksig.ApkSigner;
import com.android.apksig.ApkVerifier; import com.android.apksig.ApkVerifier;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Predicate;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.hash.Hashing;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.zane.smapiinstaller.BuildConfig; import com.zane.smapiinstaller.BuildConfig;
import com.zane.smapiinstaller.MainActivity; 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.Constants;
import com.zane.smapiinstaller.constant.DialogAction; import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.constant.ManifestPatchConstants; import com.zane.smapiinstaller.constant.ManifestPatchConstants;
import com.zane.smapiinstaller.dto.Tuple2;
import com.zane.smapiinstaller.entity.ApkFilesManifest; import com.zane.smapiinstaller.entity.ApkFilesManifest;
import com.zane.smapiinstaller.entity.ManifestEntry; import com.zane.smapiinstaller.entity.ManifestEntry;
import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.DialogUtils;
@ -47,18 +45,17 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; 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 java.util.zip.Deflater;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import pxb.android.axml.NodeVisitor; import pxb.android.axml.NodeVisitor;
/** /**
@ -108,46 +105,44 @@ public class ApkPatcher {
String sourceDir = packageInfo.applicationInfo.publicSourceDir; String sourceDir = packageInfo.applicationInfo.publicSourceDir;
File apkFile = new File(sourceDir); File apkFile = new File(sourceDir);
File externalFilesDir = Environment.getExternalStorageDirectory(); String stadewValleyBasePath = FileUtils.getStadewValleyBasePath();
if (externalFilesDir != null) { File dest = new File(stadewValleyBasePath + "/SMAPI Installer/");
File dest = new File(externalFilesDir.getAbsolutePath() + "/SMAPI Installer/"); if (!dest.exists()) {
if (!dest.exists()) { if (!dest.mkdir()) {
if (!dest.mkdir()) { errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath()));
errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath())); return null;
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 (PackageManager.NameNotFoundException ignored) {
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Extract error", e); Log.e(TAG, "Extract error", e);
@ -250,9 +245,9 @@ public class ApkPatcher {
AtomicReference<String> packageName = new AtomicReference<>(); AtomicReference<String> packageName = new AtomicReference<>();
AtomicReference<String> versionName = new AtomicReference<>(); AtomicReference<String> versionName = new AtomicReference<>();
AtomicLong versionCode = new AtomicLong(); AtomicLong versionCode = new AtomicLong();
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> { Function<ManifestTagVisitor.AttrArgs, List<ManifestTagVisitor.AttrArgs>> attrProcessLogic = (attr) -> {
if (attr == null) { if (attr == null) {
return true; return null;
} }
if (attr.type == NodeVisitor.TYPE_STRING) { if (attr.type == NodeVisitor.TYPE_STRING) {
String strObj = (String) attr.obj; String strObj = (String) attr.obj;
@ -276,6 +271,7 @@ public class ApkPatcher {
attr.obj = Constants.PATCHED_APP_NAME; attr.obj = Constants.PATCHED_APP_NAME;
} }
} }
// return Collections.singletonList(new ManifestTagVisitor.AttrArgs(attr.ns, "requestLegacyExternalStorage", -1, NodeVisitor.TYPE_INT_BOOLEAN, true));
break; break;
case "authorities": case "authorities":
if (strObj.contains(packageName.get())) { if (strObj.contains(packageName.get())) {
@ -296,10 +292,22 @@ public class ApkPatcher {
versionCode.set((int) attr.obj); 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 { try {
byte[] modifyManifest = CommonLogic.modifyManifest(bytes, processLogic); byte[] modifyManifest = CommonLogic.modifyManifest(bytes, attrProcessLogic, childProcessLogic);
if (StringUtils.endsWith(versionName.get(), ManifestPatchConstants.PATTERN_VERSION_AMAZON)) { if (StringUtils.endsWith(versionName.get(), ManifestPatchConstants.PATTERN_VERSION_AMAZON)) {
packageName.set(ManifestPatchConstants.APP_PACKAGE_NAME + 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) { public String sign(String apkPath) {
try { try {
File externalFilesDir = Environment.getExternalStorageDirectory(); String stadewValleyBasePath = FileUtils.getStadewValleyBasePath();
emitProgress(47); emitProgress(47);
if (externalFilesDir != null) { String signApkPath = stadewValleyBasePath + "/SMAPI Installer/base_signed.apk";
String signApkPath = externalFilesDir.getAbsolutePath() + "/SMAPI Installer/base_signed.apk"; KeyStore ks = new KeyStoreFileManager.JksKeyStore();
KeyStore ks = new KeyStoreFileManager.JksKeyStore(); try (InputStream fis = context.getAssets().open("debug.keystore.dat")) {
try (InputStream fis = context.getAssets().open("debug.keystore.dat")) { ks.load(fis, PASSWORD.toCharArray());
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 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) { } catch (Exception e) {
Log.e(TAG, "Sign error", e); Log.e(TAG, "Sign error", e);
errorMessage.set(e.getLocalizedMessage()); errorMessage.set(e.getLocalizedMessage());

View File

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

View File

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

View File

@ -1,7 +1,10 @@
package com.zane.smapiinstaller.logic; 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; import pxb.android.axml.NodeVisitor;
/** /**
@ -9,38 +12,56 @@ import pxb.android.axml.NodeVisitor;
*/ */
class ManifestTagVisitor extends 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); super(nv);
this.attrProcessLogic = attrProcessLogic; this.attrProcessLogic = attrProcessLogic;
this.childProcessLogic = childProcessLogic;
} }
@Override @Override
public void attr(String ns, String name, int resourceId, int type, Object obj) { public void attr(String ns, String name, int resourceId, int type, Object obj) {
AttrArgs attrArgs = new AttrArgs(ns, name, resourceId, type, 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); 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 @Override
public NodeVisitor child(String ns, String name) { 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 { public static class AttrArgs {
String ns; String ns;
String name; String name;
int resourceId; int resourceId;
int type; int type;
Object obj; 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.app.Activity;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
@ -17,16 +16,18 @@ import com.google.common.collect.Queues;
import com.lzy.okgo.OkGo; import com.lzy.okgo.OkGo;
import com.lzy.okgo.model.Response; import com.lzy.okgo.model.Response;
import com.microsoft.appcenter.crashes.Crashes; import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.MobileNavigationDirections;
import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants; import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction; import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.dto.ModUpdateCheckRequestDto; import com.zane.smapiinstaller.dto.ModUpdateCheckRequestDto;
import com.zane.smapiinstaller.dto.ModUpdateCheckResponseDto; import com.zane.smapiinstaller.dto.ModUpdateCheckResponseDto;
import com.zane.smapiinstaller.dto.Tuple2;
import com.zane.smapiinstaller.entity.ModManifestEntry; import com.zane.smapiinstaller.entity.ModManifestEntry;
import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils; import com.zane.smapiinstaller.utils.FileUtils;
import com.zane.smapiinstaller.utils.JsonUtil;
import com.zane.smapiinstaller.utils.JsonCallback; import com.zane.smapiinstaller.utils.JsonCallback;
import com.zane.smapiinstaller.utils.JsonUtil;
import com.zane.smapiinstaller.utils.VersionUtil; import com.zane.smapiinstaller.utils.VersionUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -35,17 +36,22 @@ import org.zeroturnaround.zip.ZipUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
/** /**
* Mod资源管理器 * Mod资源管理器
*
* @author Zane * @author Zane
*/ */
public class ModAssetsManager { public class ModAssetsManager {
@ -68,7 +74,7 @@ public class ModAssetsManager {
*/ */
public static ModManifestEntry findFirstModIf(Predicate<ModManifestEntry> filter) { public static ModManifestEntry findFirstModIf(Predicate<ModManifestEntry> filter) {
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue(); ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH)); files.add(new File(FileUtils.getStadewValleyBasePath(), Constants.MOD_PATH));
do { do {
File currentFile = files.poll(); File currentFile = files.poll();
if (currentFile != null && currentFile.exists()) { if (currentFile != null && currentFile.exists()) {
@ -115,7 +121,7 @@ public class ModAssetsManager {
*/ */
public static List<ModManifestEntry> findAllInstalledMods(boolean ignoreDisabledMod) { public static List<ModManifestEntry> findAllInstalledMods(boolean ignoreDisabledMod) {
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue(); 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); List<ModManifestEntry> mods = new ArrayList<>(30);
do { do {
File currentFile = files.poll(); File currentFile = files.poll();
@ -162,7 +168,7 @@ public class ModAssetsManager {
if (modManifestEntries == null) { if (modManifestEntries == null) {
return false; 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); ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
for (ModManifestEntry mod : modManifestEntries) { for (ModManifestEntry mod : modManifestEntries) {
if (installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) { if (installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) {
@ -253,18 +259,28 @@ public class ModAssetsManager {
* @param returnCallback 回调函数 * @param returnCallback 回调函数
*/ */
private void checkUnsatisfiedDependencies(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> 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)) .map(mod -> checkModDependencyError(mod, installedModMap))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (dependencyErrors.size() > 0) { if (dependencyErrors.size() > 0) {
DialogUtils.showConfirmDialog(root, R.string.error, DialogUtils.showConfirmDialog(root, R.string.error,
Joiner.on(";").join(dependencyErrors), dependencyErrors.stream().map(Tuple2::getFirst).collect(Collectors.joining(";")),
R.string.continue_text, R.string.abort, R.string.continue_text, R.string.menu_download,
((dialog, which) -> { ((dialog, which) -> {
if (which == DialogAction.POSITIVE) { if (which == DialogAction.POSITIVE) {
returnCallback.accept(true); returnCallback.accept(true);
} else { } 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); returnCallback.accept(false);
} }
})); }));
@ -280,17 +296,26 @@ public class ModAssetsManager {
* @param returnCallback 回调函数 * @param returnCallback 回调函数
*/ */
private void checkContentpacks(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> 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)) .map(mod -> checkContentPackDependencyError(mod, installedModMap))
.filter(Objects::nonNull).collect(Collectors.toList()); .filter(Objects::nonNull).collect(Collectors.toList());
if (!dependencyErrors.isEmpty()) { if (!dependencyErrors.isEmpty()) {
DialogUtils.showConfirmDialog(root, R.string.error, DialogUtils.showConfirmDialog(root, R.string.error,
Joiner.on(";").join(dependencyErrors), dependencyErrors.stream().map(Tuple2::getFirst).collect(Collectors.joining(";")),
R.string.continue_text, R.string.abort, R.string.continue_text, R.string.menu_download,
((dialog, which) -> { ((dialog, which) -> {
if (which == DialogAction.POSITIVE) { if (which == DialogAction.POSITIVE) {
returnCallback.accept(true); returnCallback.accept(true);
} else { } 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); 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) { public void checkModUpdate(Consumer<List<ModUpdateCheckResponseDto>> callback) {
if (checkUpdating.get()) { if (checkUpdating.get()) {
return; 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) { if (mod.getDependencies() != null) {
List<ModManifestEntry> unsatisfiedDependencies = mod.getDependencies().stream() List<ModManifestEntry> unsatisfiedDependencies = mod.getDependencies().stream()
.filter(dependency -> isDependencyIsExist(dependency, installedModMap)) .filter(dependency -> isDependencyIsExist(dependency, installedModMap))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (unsatisfiedDependencies.size() > 0) { 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; return null;
@ -386,7 +452,7 @@ public class ModAssetsManager {
return false; 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(); ModManifestEntry dependency = mod.getContentPackFor();
if (dependency != null) { if (dependency != null) {
if (dependency.getIsRequired() != null && !dependency.getIsRequired()) { if (dependency.getIsRequired() != null && !dependency.getIsRequired()) {
@ -403,7 +469,7 @@ public class ModAssetsManager {
} }
} }
if (entries.size() != 1) { 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(); String version = entries.get(0).getVersion();
if (!StringUtils.isBlank(version)) { if (!StringUtils.isBlank(version)) {
@ -411,7 +477,7 @@ public class ModAssetsManager {
return null; return null;
} }
if (VersionUtil.compareVersion(version, dependency.getMinimumVersion()) < 0) { 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; return null;

View File

@ -7,12 +7,19 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.BuildConfig;
import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants; import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction; import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.databinding.FragmentConfigEditBinding; import com.zane.smapiinstaller.databinding.FragmentConfigEditBinding;
import com.zane.smapiinstaller.dto.WebViewObject;
import com.zane.smapiinstaller.logic.CommonLogic; import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils; import com.zane.smapiinstaller.utils.FileUtils;
@ -21,6 +28,7 @@ import com.zane.smapiinstaller.utils.JsonUtil;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.util.Locale;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -35,30 +43,72 @@ public class ConfigEditFragment extends Fragment {
private String configPath; private String configPath;
private FragmentConfigEditBinding binding; private FragmentConfigEditBinding binding;
private WebViewObject webObject = null;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { ViewGroup container, Bundle savedInstanceState) {
binding = FragmentConfigEditBinding.inflate(inflater, container, false); 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 -> { CommonLogic.doOnNonNull(this.getArguments(), arguments -> {
ConfigEditFragmentArgs args = ConfigEditFragmentArgs.fromBundle(arguments); ConfigEditFragmentArgs args = ConfigEditFragmentArgs.fromBundle(arguments);
editable = args.getEditable(); editable = args.getEditable();
if (!editable) { if (!editable) {
binding.editTextConfigEdit.setKeyListener(null);
binding.buttonConfigSave.setVisibility(View.INVISIBLE); binding.buttonConfigSave.setVisibility(View.INVISIBLE);
binding.buttonConfigCancel.setVisibility(View.INVISIBLE); binding.buttonConfigCancel.setVisibility(View.INVISIBLE);
binding.buttonLogParser.setVisibility(View.VISIBLE); binding.buttonLogParser.setVisibility(View.VISIBLE);
} }
binding.editTextConfigWebview.getSettings().setJavaScriptEnabled(true);
binding.editTextConfigWebview.setWebChromeClient(new WebChromeClient());
binding.editTextConfigWebview.setWebViewClient(new WebViewClient());
configPath = args.getConfigPath(); configPath = args.getConfigPath();
File file = new File(configPath); File file = new File(configPath);
if (file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) { if (file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) {
String fileText = FileUtils.getFileText(file); String fileText = FileUtils.getFileText(file);
if (fileText != null) { 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 { } 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) -> { 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) { if (which == DialogAction.POSITIVE) {
Intent intent = new Intent("android.intent.action.VIEW"); 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 @Override
@ -92,11 +139,14 @@ public class ConfigEditFragment extends Fragment {
private void onConfigSave() { private void onConfigSave() {
try { try {
JsonUtil.checkJson(binding.editTextConfigEdit.getText().toString()); if(webObject != null) {
FileOutputStream outputStream = new FileOutputStream(configPath); binding.editTextConfigWebview.loadUrl("javascript:getJson()");
try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) { JsonUtil.checkJson(webObject.getText());
outputStreamWriter.write(binding.editTextConfigEdit.getText().toString()); FileOutputStream outputStream = new FileOutputStream(configPath);
outputStreamWriter.flush(); try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) {
outputStreamWriter.write(webObject.getText());
outputStreamWriter.flush();
}
} }
} catch (Exception e) { } catch (Exception e) {
DialogUtils.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage()); DialogUtils.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage());

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package com.zane.smapiinstaller.ui.install;
import android.app.Activity; import android.app.Activity;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.ui.main.MainTabsFragmentDirections;
import com.zane.smapiinstaller.utils.ConfigUtils; import com.zane.smapiinstaller.utils.ConfigUtils;
import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -55,7 +55,7 @@ public class InstallFragment extends Fragment {
binding.layoutAdvInstall.setVisibility(View.VISIBLE); binding.layoutAdvInstall.setVisibility(View.VISIBLE);
} }
try { 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)) { if (StringUtils.isNoneBlank(firstLine)) {
String versionString = RegExUtils.removePattern(firstLine, "\\[.+\\]\\s+"); String versionString = RegExUtils.removePattern(firstLine, "\\[.+\\]\\s+");
versionString = RegExUtils.removePattern(versionString, "\\s+with.+"); 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()) 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) { public ViewHolder(View view) {

View File

@ -279,7 +279,7 @@ public class FileUtils extends org.zeroturnaround.zip.commons.FileUtils {
* @return 移除前缀后的路径 * @return 移除前缀后的路径
*/ */
public static String toPrettyPath(String path) { 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; return null;
} }
public static String getStadewValleyBasePath() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { 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 "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'