| @ -0,0 +1,13 @@ | |||||||
|  | *.iml | ||||||
|  | .gradle | ||||||
|  | /local.properties | ||||||
|  | /.idea/caches | ||||||
|  | /.idea/libraries | ||||||
|  | /.idea/modules.xml | ||||||
|  | /.idea/workspace.xml | ||||||
|  | /.idea/navEditor.xml | ||||||
|  | /.idea/assetWizardSettings.xml | ||||||
|  | .DS_Store | ||||||
|  | /build | ||||||
|  | /captures | ||||||
|  | .externalNativeBuild | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | <component name="ProjectCodeStyleConfiguration"> | ||||||
|  |   <code_scheme name="Project" version="173"> | ||||||
|  |     <JetCodeStyleSettings> | ||||||
|  |       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||||
|  |     </JetCodeStyleSettings> | ||||||
|  |     <codeStyleSettings language="kotlin"> | ||||||
|  |       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||||
|  |     </codeStyleSettings> | ||||||
|  |   </code_scheme> | ||||||
|  | </component> | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | <component name="ProjectCodeStyleConfiguration"> | ||||||
|  |   <state> | ||||||
|  |     <option name="USE_PER_PROJECT_SETTINGS" value="true" /> | ||||||
|  |   </state> | ||||||
|  | </component> | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | <component name="ProjectDictionaryState"> | ||||||
|  |   <dictionary name="GKF"> | ||||||
|  |     <words> | ||||||
|  |       <w>legado</w> | ||||||
|  |     </words> | ||||||
|  |   </dictionary> | ||||||
|  | </component> | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="Encoding" addBOMForNewFiles="with NO BOM"> | ||||||
|  |     <file url="PROJECT" charset="UTF-8" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
| @ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="GradleSettings"> | ||||||
|  |     <option name="linkedExternalProjectsSettings"> | ||||||
|  |       <GradleProjectSettings> | ||||||
|  |         <option name="distributionType" value="DEFAULT_WRAPPED" /> | ||||||
|  |         <option name="externalProjectPath" value="$PROJECT_DIR$" /> | ||||||
|  |         <option name="modules"> | ||||||
|  |           <set> | ||||||
|  |             <option value="$PROJECT_DIR$" /> | ||||||
|  |             <option value="$PROJECT_DIR$/app" /> | ||||||
|  |           </set> | ||||||
|  |         </option> | ||||||
|  |         <option name="resolveModulePerSourceSet" value="false" /> | ||||||
|  |       </GradleProjectSettings> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
| @ -0,0 +1,89 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="CMakeSettings"> | ||||||
|  |     <configurations> | ||||||
|  |       <configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" /> | ||||||
|  |     </configurations> | ||||||
|  |   </component> | ||||||
|  |   <component name="MarkdownProjectSettings" wasCopied="true"> | ||||||
|  |     <PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" openRemoteLinks="true" replaceUnicodeEmoji="false" lastLayoutSetsDefault="false"> | ||||||
|  |       <PanelProvider> | ||||||
|  |         <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" /> | ||||||
|  |       </PanelProvider> | ||||||
|  |     </PreviewSettings> | ||||||
|  |     <ParserSettings gitHubSyntaxChange="false" emojiShortcuts="0" emojiImages="0"> | ||||||
|  |       <PegdownExtensions> | ||||||
|  |         <option name="ABBREVIATIONS" value="false" /> | ||||||
|  |         <option name="ANCHORLINKS" value="true" /> | ||||||
|  |         <option name="ASIDE" value="false" /> | ||||||
|  |         <option name="ATXHEADERSPACE" value="true" /> | ||||||
|  |         <option name="AUTOLINKS" value="true" /> | ||||||
|  |         <option name="DEFINITIONS" value="false" /> | ||||||
|  |         <option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" /> | ||||||
|  |         <option name="FENCED_CODE_BLOCKS" value="true" /> | ||||||
|  |         <option name="FOOTNOTES" value="false" /> | ||||||
|  |         <option name="HARDWRAPS" value="false" /> | ||||||
|  |         <option name="HTML_DEEP_PARSER" value="false" /> | ||||||
|  |         <option name="INSERTED" value="false" /> | ||||||
|  |         <option name="QUOTES" value="false" /> | ||||||
|  |         <option name="RELAXEDHRULES" value="true" /> | ||||||
|  |         <option name="SMARTS" value="false" /> | ||||||
|  |         <option name="STRIKETHROUGH" value="true" /> | ||||||
|  |         <option name="SUBSCRIPT" value="false" /> | ||||||
|  |         <option name="SUPERSCRIPT" value="false" /> | ||||||
|  |         <option name="SUPPRESS_HTML_BLOCKS" value="false" /> | ||||||
|  |         <option name="SUPPRESS_INLINE_HTML" value="false" /> | ||||||
|  |         <option name="TABLES" value="true" /> | ||||||
|  |         <option name="TASKLISTITEMS" value="true" /> | ||||||
|  |         <option name="TOC" value="false" /> | ||||||
|  |         <option name="WIKILINKS" value="true" /> | ||||||
|  |       </PegdownExtensions> | ||||||
|  |       <ParserOptions> | ||||||
|  |         <option name="ADMONITION_EXT" value="false" /> | ||||||
|  |         <option name="ATTRIBUTES_EXT" value="false" /> | ||||||
|  |         <option name="COMMONMARK_LISTS" value="true" /> | ||||||
|  |         <option name="DUMMY" value="false" /> | ||||||
|  |         <option name="EMOJI_SHORTCUTS" value="true" /> | ||||||
|  |         <option name="ENUMERATED_REFERENCES_EXT" value="false" /> | ||||||
|  |         <option name="FLEXMARK_FRONT_MATTER" value="false" /> | ||||||
|  |         <option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" /> | ||||||
|  |         <option name="GFM_TABLE_RENDERING" value="true" /> | ||||||
|  |         <option name="GITBOOK_URL_ENCODING" value="false" /> | ||||||
|  |         <option name="GITHUB_LISTS" value="false" /> | ||||||
|  |         <option name="GITHUB_WIKI_LINKS" value="true" /> | ||||||
|  |         <option name="HEADER_ID_NO_DUPED_DASHES" value="false" /> | ||||||
|  |         <option name="JEKYLL_FRONT_MATTER" value="false" /> | ||||||
|  |         <option name="NO_TEXT_ATTRIBUTES" value="false" /> | ||||||
|  |         <option name="PARSE_HTML_ANCHOR_ID" value="false" /> | ||||||
|  |         <option name="SIM_TOC_BLANK_LINE_SPACER" value="true" /> | ||||||
|  |       </ParserOptions> | ||||||
|  |     </ParserSettings> | ||||||
|  |     <HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false" imageUriSerials="false"> | ||||||
|  |       <GeneratorProvider> | ||||||
|  |         <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" /> | ||||||
|  |       </GeneratorProvider> | ||||||
|  |       <headerTop /> | ||||||
|  |       <headerBottom /> | ||||||
|  |       <bodyTop /> | ||||||
|  |       <bodyBottom /> | ||||||
|  |     </HtmlSettings> | ||||||
|  |     <CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true"> | ||||||
|  |       <StylesheetProvider> | ||||||
|  |         <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" /> | ||||||
|  |       </StylesheetProvider> | ||||||
|  |       <ScriptProviders /> | ||||||
|  |       <cssText /> | ||||||
|  |       <cssUriHistory /> | ||||||
|  |     </CssSettings> | ||||||
|  |     <HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetExt="" useTargetExt="false" noCssNoScripts="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" /> | ||||||
|  |     <LinkMapSettings> | ||||||
|  |       <textMaps /> | ||||||
|  |     </LinkMapSettings> | ||||||
|  |   </component> | ||||||
|  |   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | ||||||
|  |     <output url="file://$PROJECT_DIR$/build/classes" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="ProjectType"> | ||||||
|  |     <option name="id" value="Android" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="RunConfigurationProducerService"> | ||||||
|  |     <option name="ignoredProducers"> | ||||||
|  |       <set> | ||||||
|  |         <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> | ||||||
|  |         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> | ||||||
|  |         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> | ||||||
|  |       </set> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
| @ -0,0 +1 @@ | |||||||
|  | /build | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | apply plugin: 'com.android.application' | ||||||
|  | 
 | ||||||
|  | apply plugin: 'kotlin-android' | ||||||
|  | 
 | ||||||
|  | apply plugin: 'kotlin-android-extensions' | ||||||
|  | 
 | ||||||
|  | android { | ||||||
|  |     compileSdkVersion 28 | ||||||
|  |     defaultConfig { | ||||||
|  |         applicationId "io.legado.book" | ||||||
|  |         minSdkVersion 21 | ||||||
|  |         targetSdkVersion 28 | ||||||
|  |         versionCode 1 | ||||||
|  |         versionName "1.0" | ||||||
|  |         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||||
|  |     } | ||||||
|  |     buildTypes { | ||||||
|  |         release { | ||||||
|  |             minifyEnabled false | ||||||
|  |             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | dependencies { | ||||||
|  |     implementation fileTree(dir: 'libs', include: ['*.jar']) | ||||||
|  |     implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||||
|  |     implementation 'androidx.appcompat:appcompat:1.0.2' | ||||||
|  |     implementation 'androidx.core:core-ktx:1.0.2' | ||||||
|  |     implementation 'androidx.legacy:legacy-support-v4:1.0.0' | ||||||
|  |     implementation 'com.google.android.material:material:1.0.0' | ||||||
|  |     implementation 'androidx.constraintlayout:constraintlayout:1.1.3' | ||||||
|  |     testImplementation 'junit:junit:4.12' | ||||||
|  |     androidTestImplementation 'androidx.test:runner:1.1.1' | ||||||
|  |     androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' | ||||||
|  | } | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | # Add project specific ProGuard rules here. | ||||||
|  | # You can control the set of applied configuration files using the | ||||||
|  | # proguardFiles setting in build.gradle. | ||||||
|  | # | ||||||
|  | # For more details, see | ||||||
|  | #   http://developer.android.com/guide/developing/tools/proguard.html | ||||||
|  | 
 | ||||||
|  | # If your project uses WebView with JS, uncomment the following | ||||||
|  | # and specify the fully qualified class name to the JavaScript interface | ||||||
|  | # class: | ||||||
|  | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||||
|  | #   public *; | ||||||
|  | #} | ||||||
|  | 
 | ||||||
|  | # Uncomment this to preserve the line number information for | ||||||
|  | # debugging stack traces. | ||||||
|  | #-keepattributes SourceFile,LineNumberTable | ||||||
|  | 
 | ||||||
|  | # If you keep the line number information, uncomment this to | ||||||
|  | # hide the original source file name. | ||||||
|  | #-renamesourcefileattribute SourceFile | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | package io.legado.book | ||||||
|  | 
 | ||||||
|  | import androidx.test.InstrumentationRegistry | ||||||
|  | import androidx.test.runner.AndroidJUnit4 | ||||||
|  | 
 | ||||||
|  | import org.junit.Test | ||||||
|  | import org.junit.runner.RunWith | ||||||
|  | 
 | ||||||
|  | import org.junit.Assert.* | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Instrumented test, which will execute on an Android device. | ||||||
|  |  * | ||||||
|  |  * See [testing documentation](http://d.android.com/tools/testing). | ||||||
|  |  */ | ||||||
|  | @RunWith(AndroidJUnit4::class) | ||||||
|  | class ExampleInstrumentedTest { | ||||||
|  |     @Test | ||||||
|  |     fun useAppContext() { | ||||||
|  |         // Context of the app under test. | ||||||
|  |         val appContext = InstrumentationRegistry.getTargetContext() | ||||||
|  |         assertEquals("cn.legado.book", appContext.packageName) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,41 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |           xmlns:tools="http://schemas.android.com/tools" package="io.legado.book"> | ||||||
|  | 
 | ||||||
|  |     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||||||
|  |     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|  |     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> | ||||||
|  |     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> | ||||||
|  |     <uses-permission android:name="android.permission.READ_PHONE_STATE"/> | ||||||
|  |     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> | ||||||
|  |     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> | ||||||
|  |     <uses-permission android:name="android.permission.GET_ACCOUNTS"/> | ||||||
|  |     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> | ||||||
|  |     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> | ||||||
|  |     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> | ||||||
|  | 
 | ||||||
|  |     <application | ||||||
|  |             android:name=".App" | ||||||
|  |             android:allowBackup="true" | ||||||
|  |             android:icon="@mipmap/ic_launcher" | ||||||
|  |             android:label="@string/app_name" | ||||||
|  |             android:roundIcon="@mipmap/ic_launcher_round" | ||||||
|  |             android:supportsRtl="true" | ||||||
|  |             android:theme="@style/AppTheme" | ||||||
|  |             tools:ignore="AllowBackup,GoogleAppIndexingWarning"> | ||||||
|  | 
 | ||||||
|  |         <activity | ||||||
|  |                 android:name="io.legado.book.view.MainActivity" | ||||||
|  |                 android:label="@string/app_name" | ||||||
|  |                 android:theme="@style/AppTheme.NoActionBar"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN"/> | ||||||
|  | 
 | ||||||
|  |                 <category android:name="android.intent.category.LAUNCHER"/> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity> | ||||||
|  | 
 | ||||||
|  |     </application> | ||||||
|  | 
 | ||||||
|  | </manifest> | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | package io.legado.book | ||||||
|  | 
 | ||||||
|  | import android.app.Application | ||||||
|  | 
 | ||||||
|  | class App : Application() { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     override fun onCreate() { | ||||||
|  |         super.onCreate() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,818 @@ | |||||||
|  | //Copyright (c) 2017. 章钦豪. All rights reserved.
 | ||||||
|  | package io.legado.book.utils; | ||||||
|  | 
 | ||||||
|  | import android.content.Context; | ||||||
|  | import android.graphics.Bitmap; | ||||||
|  | import android.graphics.BitmapFactory; | ||||||
|  | import android.graphics.Canvas; | ||||||
|  | import android.graphics.PixelFormat; | ||||||
|  | import android.graphics.drawable.BitmapDrawable; | ||||||
|  | import android.graphics.drawable.Drawable; | ||||||
|  | import org.json.JSONArray; | ||||||
|  | import org.json.JSONObject; | ||||||
|  | import timber.log.Timber; | ||||||
|  | 
 | ||||||
|  | import java.io.*; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Map.Entry; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.concurrent.atomic.AtomicInteger; | ||||||
|  | import java.util.concurrent.atomic.AtomicLong; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 本地缓存 | ||||||
|  |  */ | ||||||
|  | @SuppressWarnings({"unused", "ResultOfMethodCallIgnored", "WeakerAccess"}) | ||||||
|  | public class ACache { | ||||||
|  |     public static final int TIME_HOUR = 60 * 60; | ||||||
|  |     public static final int TIME_DAY = TIME_HOUR * 24; | ||||||
|  |     private static final int MAX_SIZE = 1000 * 1000 * 50; // 50 mb
 | ||||||
|  |     private static final int MAX_COUNT = Integer.MAX_VALUE; // 不限制存放数据的数量
 | ||||||
|  |     private static Map<String, ACache> mInstanceMap = new HashMap<>(); | ||||||
|  |     private ACacheManager mCache; | ||||||
|  | 
 | ||||||
|  |     private ACache(File cacheDir, long max_size, int max_count) { | ||||||
|  |         try { | ||||||
|  |             if (!cacheDir.exists() && !cacheDir.mkdirs()) { | ||||||
|  |                 Timber.tag("ACache").i("can't make dirs in %s", cacheDir.getAbsolutePath()); | ||||||
|  |             } | ||||||
|  |             mCache = new ACacheManager(cacheDir, max_size, max_count); | ||||||
|  |         } catch (Exception ignored) { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ACache get(Context ctx) { | ||||||
|  |         return get(ctx, "ACache"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ACache get(Context ctx, String cacheName) { | ||||||
|  |         File f = new File(ctx.getCacheDir(), cacheName); | ||||||
|  |         return get(f, MAX_SIZE, MAX_COUNT); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ACache get(File cacheDir) { | ||||||
|  |         return get(cacheDir, MAX_SIZE, MAX_COUNT); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ACache get(Context ctx, long max_zise, int max_count) { | ||||||
|  |         try { | ||||||
|  |             File f = new File(ctx.getCacheDir(), "ACache"); | ||||||
|  |             return get(f, max_zise, max_count); | ||||||
|  |         } catch (Exception ignored) { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ACache get(File cacheDir, long max_zise, int max_count) { | ||||||
|  |         try { | ||||||
|  |             ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid()); | ||||||
|  |             if (manager == null) { | ||||||
|  |                 manager = new ACache(cacheDir, max_zise, max_count); | ||||||
|  |                 mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager); | ||||||
|  |             } | ||||||
|  |             return manager; | ||||||
|  |         } catch (Exception ignored) { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static String myPid() { | ||||||
|  |         return "_" + android.os.Process.myPid(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ============ String数据 读写 ==============
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 String数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的String数据 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, String value) { | ||||||
|  |         try { | ||||||
|  |             File file = mCache.newFile(key); | ||||||
|  |             BufferedWriter out = null; | ||||||
|  |             try { | ||||||
|  |                 out = new BufferedWriter(new FileWriter(file), 1024); | ||||||
|  |                 out.write(value); | ||||||
|  |             } catch (IOException ignored) { | ||||||
|  |             } finally { | ||||||
|  |                 if (out != null) { | ||||||
|  |                     try { | ||||||
|  |                         out.flush(); | ||||||
|  |                         out.close(); | ||||||
|  |                     } catch (IOException ignored) { | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 mCache.put(file); | ||||||
|  |             } | ||||||
|  |         } catch (Exception ignored) { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 String数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的String数据 | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, String value, int saveTime) { | ||||||
|  |         put(key, Utils.newStringWithDateInfo(saveTime, value)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 读取 String数据 | ||||||
|  |      * @return String 数据 | ||||||
|  |      */ | ||||||
|  |     public String getAsString(String key) { | ||||||
|  |         File file = mCache.get(key); | ||||||
|  |         if (!file.exists()) | ||||||
|  |             return null; | ||||||
|  |         boolean removeFile = false; | ||||||
|  |         try (BufferedReader in = new BufferedReader(new FileReader(file))) { | ||||||
|  |             StringBuilder readString = new StringBuilder(); | ||||||
|  |             String currentLine; | ||||||
|  |             while ((currentLine = in.readLine()) != null) { | ||||||
|  |                 readString.append(currentLine); | ||||||
|  |             } | ||||||
|  |             if (!Utils.isDue(readString.toString())) { | ||||||
|  |                 return Utils.clearDateInfo(readString.toString()); | ||||||
|  |             } else { | ||||||
|  |                 removeFile = true; | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             return null; | ||||||
|  |         } finally { | ||||||
|  |             if (removeFile) | ||||||
|  |                 remove(key); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ========== JSONObject 数据 读写 =========
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 JSONObject数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的JSON数据 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, JSONObject value) { | ||||||
|  |         put(key, value.toString()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 JSONObject数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的JSONObject数据 | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, JSONObject value, int saveTime) { | ||||||
|  |         put(key, value.toString(), saveTime); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 读取JSONObject数据 | ||||||
|  |      * @return JSONObject数据 | ||||||
|  |      */ | ||||||
|  |     public JSONObject getAsJSONObject(String key) { | ||||||
|  |         String JSONString = getAsString(key); | ||||||
|  |         try { | ||||||
|  |             return new JSONObject(JSONString); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ============ JSONArray 数据 读写 =============
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 JSONArray数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的JSONArray数据 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, JSONArray value) { | ||||||
|  |         put(key, value.toString()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 JSONArray数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的JSONArray数据 | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, JSONArray value, int saveTime) { | ||||||
|  |         put(key, value.toString(), saveTime); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 读取JSONArray数据 | ||||||
|  |      * @return JSONArray数据 | ||||||
|  |      */ | ||||||
|  |     public JSONArray getAsJSONArray(String key) { | ||||||
|  |         String JSONString = getAsString(key); | ||||||
|  |         try { | ||||||
|  |             return new JSONArray(JSONString); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ============== byte 数据 读写 =============
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 byte数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的数据 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, byte[] value) { | ||||||
|  |         File file = mCache.newFile(key); | ||||||
|  |         FileOutputStream out = null; | ||||||
|  |         try { | ||||||
|  |             out = new FileOutputStream(file); | ||||||
|  |             out.write(value); | ||||||
|  |         } catch (Exception ignored) { | ||||||
|  |         } finally { | ||||||
|  |             if (out != null) { | ||||||
|  |                 try { | ||||||
|  |                     out.flush(); | ||||||
|  |                     out.close(); | ||||||
|  |                 } catch (IOException ignored) { | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             mCache.put(file); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 byte数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的数据 | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, byte[] value, int saveTime) { | ||||||
|  |         put(key, Utils.newByteArrayWithDateInfo(saveTime, value)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 获取 byte 数据 | ||||||
|  |      * @return byte 数据 | ||||||
|  |      */ | ||||||
|  |     public byte[] getAsBinary(String key) { | ||||||
|  |         RandomAccessFile RAFile = null; | ||||||
|  |         boolean removeFile = false; | ||||||
|  |         try { | ||||||
|  |             File file = mCache.get(key); | ||||||
|  |             if (!file.exists()) | ||||||
|  |                 return null; | ||||||
|  |             RAFile = new RandomAccessFile(file, "r"); | ||||||
|  |             byte[] byteArray = new byte[(int) RAFile.length()]; | ||||||
|  |             RAFile.read(byteArray); | ||||||
|  |             if (!Utils.isDue(byteArray)) { | ||||||
|  |                 return Utils.clearDateInfo(byteArray); | ||||||
|  |             } else { | ||||||
|  |                 removeFile = true; | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             return null; | ||||||
|  |         } finally { | ||||||
|  |             if (RAFile != null) { | ||||||
|  |                 try { | ||||||
|  |                     RAFile.close(); | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (removeFile) | ||||||
|  |                 remove(key); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ============= 序列化 数据 读写 ===============
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 Serializable数据 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的value | ||||||
|  |      */ | ||||||
|  |     public void put(String key, Serializable value) { | ||||||
|  |         put(key, value, -1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 Serializable数据到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的value | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, Serializable value, int saveTime) { | ||||||
|  |         ByteArrayOutputStream baos; | ||||||
|  |         baos = new ByteArrayOutputStream(); | ||||||
|  |         try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { | ||||||
|  |             oos.writeObject(value); | ||||||
|  |             byte[] data = baos.toByteArray(); | ||||||
|  |             if (saveTime != -1) { | ||||||
|  |                 put(key, data, saveTime); | ||||||
|  |             } else { | ||||||
|  |                 put(key, data); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 读取 Serializable数据 | ||||||
|  |      * @return Serializable 数据 | ||||||
|  |      */ | ||||||
|  |     public Object getAsObject(String key) { | ||||||
|  |         byte[] data = getAsBinary(key); | ||||||
|  |         if (data != null) { | ||||||
|  |             ByteArrayInputStream bais = null; | ||||||
|  |             ObjectInputStream ois = null; | ||||||
|  |             try { | ||||||
|  |                 bais = new ByteArrayInputStream(data); | ||||||
|  |                 ois = new ObjectInputStream(bais); | ||||||
|  |                 return ois.readObject(); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |                 return null; | ||||||
|  |             } finally { | ||||||
|  |                 try { | ||||||
|  |                     if (bais != null) | ||||||
|  |                         bais.close(); | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |                 try { | ||||||
|  |                     if (ois != null) | ||||||
|  |                         ois.close(); | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ============== bitmap 数据 读写 =============
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 bitmap 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的bitmap数据 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, Bitmap value) { | ||||||
|  |         put(key, Utils.Bitmap2Bytes(value)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 bitmap 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的 bitmap 数据 | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, Bitmap value, int saveTime) { | ||||||
|  |         put(key, Utils.Bitmap2Bytes(value), saveTime); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 读取 bitmap 数据 | ||||||
|  |      * @return bitmap 数据 | ||||||
|  |      */ | ||||||
|  |     public Bitmap getAsBitmap(String key) { | ||||||
|  |         if (getAsBinary(key) == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return Utils.Bytes2Bimap(getAsBinary(key)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // =======================================
 | ||||||
|  |     // ============= drawable 数据 读写 =============
 | ||||||
|  |     // =======================================
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 drawable 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key   保存的key | ||||||
|  |      * @param value 保存的drawable数据 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, Drawable value) { | ||||||
|  |         put(key, Utils.drawable2Bitmap(value)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 保存 drawable 到 缓存中 | ||||||
|  |      * | ||||||
|  |      * @param key      保存的key | ||||||
|  |      * @param value    保存的 drawable 数据 | ||||||
|  |      * @param saveTime 保存的时间,单位:秒 | ||||||
|  |      */ | ||||||
|  |     public void put(String key, Drawable value, int saveTime) { | ||||||
|  |         put(key, Utils.drawable2Bitmap(value), saveTime); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 读取 Drawable 数据 | ||||||
|  |      * @return Drawable 数据 | ||||||
|  |      */ | ||||||
|  |     public Drawable getAsDrawable(String key) { | ||||||
|  |         if (getAsBinary(key) == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return Utils.bitmap2Drawable(Utils.Bytes2Bimap(getAsBinary(key))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 获取缓存文件 | ||||||
|  |      * @return value 缓存的文件 | ||||||
|  |      */ | ||||||
|  |     public File file(String key) { | ||||||
|  |         try { | ||||||
|  |             File f = mCache.newFile(key); | ||||||
|  |             if (f.exists()) { | ||||||
|  |                 return f; | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 移除某个key | ||||||
|  |      * @return 是否移除成功 | ||||||
|  |      */ | ||||||
|  |     public boolean remove(String key) { | ||||||
|  |         return mCache.remove(key); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 清除所有数据 | ||||||
|  |      */ | ||||||
|  |     public void clear() { | ||||||
|  |         mCache.clear(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @author 杨福海(michael) www.yangfuhai.com | ||||||
|  |      * @version 1.0 | ||||||
|  |      * @title 时间计算工具类 | ||||||
|  |      */ | ||||||
|  |     private static class Utils { | ||||||
|  | 
 | ||||||
|  |         private static final char mSeparator = ' '; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * 判断缓存的String数据是否到期 | ||||||
|  |          * @return true:到期了 false:还没有到期 | ||||||
|  |          */ | ||||||
|  |         private static boolean isDue(String str) { | ||||||
|  |             return isDue(str.getBytes()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * 判断缓存的byte数据是否到期 | ||||||
|  |          * @return true:到期了 false:还没有到期 | ||||||
|  |          */ | ||||||
|  |         private static boolean isDue(byte[] data) { | ||||||
|  |             try { | ||||||
|  |                 String[] strs = getDateInfoFromDate(data); | ||||||
|  |                 if (strs != null && strs.length == 2) { | ||||||
|  |                     String saveTimeStr = strs[0]; | ||||||
|  |                     while (saveTimeStr.startsWith("0")) { | ||||||
|  |                         saveTimeStr = saveTimeStr | ||||||
|  |                                 .substring(1); | ||||||
|  |                     } | ||||||
|  |                     long saveTime = Long.valueOf(saveTimeStr); | ||||||
|  |                     long deleteAfter = Long.valueOf(strs[1]); | ||||||
|  |                     if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) { | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static String newStringWithDateInfo(int second, String strInfo) { | ||||||
|  |             return createDateInfo(second) + strInfo; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static byte[] newByteArrayWithDateInfo(int second, byte[] data2) { | ||||||
|  |             byte[] data1 = createDateInfo(second).getBytes(); | ||||||
|  |             byte[] retdata = new byte[data1.length + data2.length]; | ||||||
|  |             System.arraycopy(data1, 0, retdata, 0, data1.length); | ||||||
|  |             System.arraycopy(data2, 0, retdata, data1.length, data2.length); | ||||||
|  |             return retdata; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static String clearDateInfo(String strInfo) { | ||||||
|  |             if (strInfo != null && hasDateInfo(strInfo.getBytes())) { | ||||||
|  |                 strInfo = strInfo.substring(strInfo.indexOf(mSeparator) + 1); | ||||||
|  |             } | ||||||
|  |             return strInfo; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static byte[] clearDateInfo(byte[] data) { | ||||||
|  |             if (hasDateInfo(data)) { | ||||||
|  |                 return copyOfRange(data, indexOf(data, mSeparator) + 1, | ||||||
|  |                         data.length); | ||||||
|  |             } | ||||||
|  |             return data; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static boolean hasDateInfo(byte[] data) { | ||||||
|  |             return data != null && data.length > 15 && data[13] == '-' | ||||||
|  |                     && indexOf(data, mSeparator) > 14; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static String[] getDateInfoFromDate(byte[] data) { | ||||||
|  |             if (hasDateInfo(data)) { | ||||||
|  |                 String saveDate = new String(copyOfRange(data, 0, 13)); | ||||||
|  |                 String deleteAfter = new String(copyOfRange(data, 14, | ||||||
|  |                         indexOf(data, mSeparator))); | ||||||
|  |                 return new String[]{saveDate, deleteAfter}; | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static int indexOf(byte[] data, char c) { | ||||||
|  |             for (int i = 0; i < data.length; i++) { | ||||||
|  |                 if (data[i] == c) { | ||||||
|  |                     return i; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static byte[] copyOfRange(byte[] original, int from, int to) { | ||||||
|  |             int newLength = to - from; | ||||||
|  |             if (newLength < 0) | ||||||
|  |                 throw new IllegalArgumentException(from + " > " + to); | ||||||
|  |             byte[] copy = new byte[newLength]; | ||||||
|  |             System.arraycopy(original, from, copy, 0, | ||||||
|  |                     Math.min(original.length - from, newLength)); | ||||||
|  |             return copy; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static String createDateInfo(int second) { | ||||||
|  |             StringBuilder currentTime = new StringBuilder(System.currentTimeMillis() + ""); | ||||||
|  |             while (currentTime.length() < 13) { | ||||||
|  |                 currentTime.insert(0, "0"); | ||||||
|  |             } | ||||||
|  |             return currentTime + "-" + second + mSeparator; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * Bitmap → byte[] | ||||||
|  |          */ | ||||||
|  |         private static byte[] Bitmap2Bytes(Bitmap bm) { | ||||||
|  |             if (bm == null) { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |             ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||||||
|  |             bm.compress(Bitmap.CompressFormat.PNG, 100, baos); | ||||||
|  |             return baos.toByteArray(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * byte[] → Bitmap | ||||||
|  |          */ | ||||||
|  |         private static Bitmap Bytes2Bimap(byte[] b) { | ||||||
|  |             if (b.length == 0) { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |             return BitmapFactory.decodeByteArray(b, 0, b.length); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * Drawable → Bitmap | ||||||
|  |          */ | ||||||
|  |         private static Bitmap drawable2Bitmap(Drawable drawable) { | ||||||
|  |             if (drawable == null) { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |             // 取 drawable 的长宽
 | ||||||
|  |             int w = drawable.getIntrinsicWidth(); | ||||||
|  |             int h = drawable.getIntrinsicHeight(); | ||||||
|  |             // 取 drawable 的颜色格式
 | ||||||
|  |             Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 | ||||||
|  |                     : Bitmap.Config.RGB_565; | ||||||
|  |             // 建立对应 bitmap
 | ||||||
|  |             Bitmap bitmap = Bitmap.createBitmap(w, h, config); | ||||||
|  |             // 建立对应 bitmap 的画布
 | ||||||
|  |             Canvas canvas = new Canvas(bitmap); | ||||||
|  |             drawable.setBounds(0, 0, w, h); | ||||||
|  |             // 把 drawable 内容画到画布中
 | ||||||
|  |             drawable.draw(canvas); | ||||||
|  |             return bitmap; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * Bitmap → Drawable | ||||||
|  |          */ | ||||||
|  |         @SuppressWarnings("deprecation") | ||||||
|  |         private static Drawable bitmap2Drawable(Bitmap bm) { | ||||||
|  |             if (bm == null) { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |             return new BitmapDrawable(bm); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @author 杨福海(michael) www.yangfuhai.com | ||||||
|  |      * @version 1.0 | ||||||
|  |      * @title 缓存管理器 | ||||||
|  |      */ | ||||||
|  |     public class ACacheManager { | ||||||
|  |         private final AtomicLong cacheSize; | ||||||
|  |         private final AtomicInteger cacheCount; | ||||||
|  |         private final long sizeLimit; | ||||||
|  |         private final int countLimit; | ||||||
|  |         private final Map<File, Long> lastUsageDates = Collections | ||||||
|  |                 .synchronizedMap(new HashMap<>()); | ||||||
|  |         protected File cacheDir; | ||||||
|  | 
 | ||||||
|  |         private ACacheManager(File cacheDir, long sizeLimit, int countLimit) { | ||||||
|  |             this.cacheDir = cacheDir; | ||||||
|  |             this.sizeLimit = sizeLimit; | ||||||
|  |             this.countLimit = countLimit; | ||||||
|  |             cacheSize = new AtomicLong(); | ||||||
|  |             cacheCount = new AtomicInteger(); | ||||||
|  |             calculateCacheSizeAndCacheCount(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * 计算 cacheSize和cacheCount | ||||||
|  |          */ | ||||||
|  |         private void calculateCacheSizeAndCacheCount() { | ||||||
|  |             new Thread(() -> { | ||||||
|  | 
 | ||||||
|  |                 try { | ||||||
|  |                     int size = 0; | ||||||
|  |                     int count = 0; | ||||||
|  |                     File[] cachedFiles = cacheDir.listFiles(); | ||||||
|  |                     if (cachedFiles != null) { | ||||||
|  |                         for (File cachedFile : cachedFiles) { | ||||||
|  |                             size += calculateSize(cachedFile); | ||||||
|  |                             count += 1; | ||||||
|  |                             lastUsageDates.put(cachedFile, | ||||||
|  |                                     cachedFile.lastModified()); | ||||||
|  |                         } | ||||||
|  |                         cacheSize.set(size); | ||||||
|  |                         cacheCount.set(count); | ||||||
|  |                     } | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             }).start(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private void put(File file) { | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |                 int curCacheCount = cacheCount.get(); | ||||||
|  |                 while (curCacheCount + 1 > countLimit) { | ||||||
|  |                     long freedSize = removeNext(); | ||||||
|  |                     cacheSize.addAndGet(-freedSize); | ||||||
|  | 
 | ||||||
|  |                     curCacheCount = cacheCount.addAndGet(-1); | ||||||
|  |                 } | ||||||
|  |                 cacheCount.addAndGet(1); | ||||||
|  | 
 | ||||||
|  |                 long valueSize = calculateSize(file); | ||||||
|  |                 long curCacheSize = cacheSize.get(); | ||||||
|  |                 while (curCacheSize + valueSize > sizeLimit) { | ||||||
|  |                     long freedSize = removeNext(); | ||||||
|  |                     curCacheSize = cacheSize.addAndGet(-freedSize); | ||||||
|  |                 } | ||||||
|  |                 cacheSize.addAndGet(valueSize); | ||||||
|  | 
 | ||||||
|  |                 long currentTime = System.currentTimeMillis(); | ||||||
|  |                 file.setLastModified(currentTime); | ||||||
|  |                 lastUsageDates.put(file, currentTime); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private File get(String key) { | ||||||
|  |             File file = newFile(key); | ||||||
|  |             long currentTime = System.currentTimeMillis(); | ||||||
|  |             file.setLastModified(currentTime); | ||||||
|  |             lastUsageDates.put(file, currentTime); | ||||||
|  | 
 | ||||||
|  |             return file; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private File newFile(String key) { | ||||||
|  |             return new File(cacheDir, key.hashCode() + ""); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private boolean remove(String key) { | ||||||
|  |             File image = get(key); | ||||||
|  |             return image.delete(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private void clear() { | ||||||
|  |             try { | ||||||
|  |                 lastUsageDates.clear(); | ||||||
|  |                 cacheSize.set(0); | ||||||
|  |                 File[] files = cacheDir.listFiles(); | ||||||
|  |                 if (files != null) { | ||||||
|  |                     for (File f : files) { | ||||||
|  |                         f.delete(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * 移除旧的文件 | ||||||
|  |          */ | ||||||
|  |         private long removeNext() { | ||||||
|  |             try { | ||||||
|  |                 if (lastUsageDates.isEmpty()) { | ||||||
|  |                     return 0; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 Long oldestUsage = null; | ||||||
|  |                 File mostLongUsedFile = null; | ||||||
|  |                 Set<Entry<File, Long>> entries = lastUsageDates.entrySet(); | ||||||
|  |                 synchronized (lastUsageDates) { | ||||||
|  |                     for (Entry<File, Long> entry : entries) { | ||||||
|  |                         if (mostLongUsedFile == null) { | ||||||
|  |                             mostLongUsedFile = entry.getKey(); | ||||||
|  |                             oldestUsage = entry.getValue(); | ||||||
|  |                         } else { | ||||||
|  |                             Long lastValueUsage = entry.getValue(); | ||||||
|  |                             if (lastValueUsage < oldestUsage) { | ||||||
|  |                                 oldestUsage = lastValueUsage; | ||||||
|  |                                 mostLongUsedFile = entry.getKey(); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 long fileSize = 0; | ||||||
|  |                 if (mostLongUsedFile != null) { | ||||||
|  |                     fileSize = calculateSize(mostLongUsedFile); | ||||||
|  |                     if (mostLongUsedFile.delete()) { | ||||||
|  |                         lastUsageDates.remove(mostLongUsedFile); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return fileSize; | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private long calculateSize(File file) { | ||||||
|  |             return file.length(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,94 @@ | |||||||
|  | package io.legado.book.view | ||||||
|  | 
 | ||||||
|  | import android.os.Bundle | ||||||
|  | import com.google.android.material.floatingactionbutton.FloatingActionButton | ||||||
|  | import com.google.android.material.snackbar.Snackbar | ||||||
|  | import androidx.core.view.GravityCompat | ||||||
|  | import androidx.appcompat.app.ActionBarDrawerToggle | ||||||
|  | import android.view.MenuItem | ||||||
|  | import androidx.drawerlayout.widget.DrawerLayout | ||||||
|  | import com.google.android.material.navigation.NavigationView | ||||||
|  | import androidx.appcompat.app.AppCompatActivity | ||||||
|  | import androidx.appcompat.widget.Toolbar | ||||||
|  | import android.view.Menu | ||||||
|  | import io.legado.book.R | ||||||
|  | 
 | ||||||
|  | class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|  |         super.onCreate(savedInstanceState) | ||||||
|  |         setContentView(R.layout.activity_main) | ||||||
|  |         val toolbar: Toolbar = findViewById(R.id.toolbar) | ||||||
|  |         setSupportActionBar(toolbar) | ||||||
|  | 
 | ||||||
|  |         val fab: FloatingActionButton = findViewById(R.id.fab) | ||||||
|  |         fab.setOnClickListener { view -> | ||||||
|  |             Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) | ||||||
|  |                 .setAction("Action", null).show() | ||||||
|  |         } | ||||||
|  |         val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) | ||||||
|  |         val navView: NavigationView = findViewById(R.id.nav_view) | ||||||
|  |         val toggle = ActionBarDrawerToggle( | ||||||
|  |             this, drawerLayout, toolbar, | ||||||
|  |             R.string.navigation_drawer_open, | ||||||
|  |             R.string.navigation_drawer_close | ||||||
|  |         ) | ||||||
|  |         drawerLayout.addDrawerListener(toggle) | ||||||
|  |         toggle.syncState() | ||||||
|  | 
 | ||||||
|  |         navView.setNavigationItemSelectedListener(this) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onBackPressed() { | ||||||
|  |         val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) | ||||||
|  |         if (drawerLayout.isDrawerOpen(GravityCompat.START)) { | ||||||
|  |             drawerLayout.closeDrawer(GravityCompat.START) | ||||||
|  |         } else { | ||||||
|  |             super.onBackPressed() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||||
|  |         // Inflate the menu; this adds items to the action bar if it is present. | ||||||
|  |         menuInflater.inflate(R.menu.main, menu) | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||||
|  |         // Handle action bar item clicks here. The action bar will | ||||||
|  |         // automatically handle clicks on the Home/Up button, so long | ||||||
|  |         // as you specify a parent activity in AndroidManifest.xml. | ||||||
|  |         return when (item.itemId) { | ||||||
|  |             R.id.action_settings -> true | ||||||
|  |             else -> super.onOptionsItemSelected(item) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onNavigationItemSelected(item: MenuItem): Boolean { | ||||||
|  |         // Handle navigation view item clicks here. | ||||||
|  |         when (item.itemId) { | ||||||
|  |             R.id.nav_home -> { | ||||||
|  |                 // Handle the camera action | ||||||
|  |             } | ||||||
|  |             R.id.nav_gallery -> { | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |             R.id.nav_slideshow -> { | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |             R.id.nav_tools -> { | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |             R.id.nav_share -> { | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |             R.id.nav_send -> { | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) | ||||||
|  |         drawerLayout.closeDrawer(GravityCompat.START) | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,34 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:aapt="http://schemas.android.com/aapt" | ||||||
|  |         android:width="108dp" | ||||||
|  |         android:height="108dp" | ||||||
|  |         android:viewportHeight="108" | ||||||
|  |         android:viewportWidth="108"> | ||||||
|  |     <path | ||||||
|  |             android:fillType="evenOdd" | ||||||
|  |             android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" | ||||||
|  |             android:strokeColor="#00000000" | ||||||
|  |             android:strokeWidth="1"> | ||||||
|  |         <aapt:attr name="android:fillColor"> | ||||||
|  |             <gradient | ||||||
|  |                     android:endX="78.5885" | ||||||
|  |                     android:endY="90.9159" | ||||||
|  |                     android:startX="48.7653" | ||||||
|  |                     android:startY="61.0927" | ||||||
|  |                     android:type="linear"> | ||||||
|  |                 <item | ||||||
|  |                         android:color="#44000000" | ||||||
|  |                         android:offset="0.0"/> | ||||||
|  |                 <item | ||||||
|  |                         android:color="#00000000" | ||||||
|  |                         android:offset="1.0"/> | ||||||
|  |             </gradient> | ||||||
|  |         </aapt:attr> | ||||||
|  |     </path> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FFFFFF" | ||||||
|  |             android:fillType="nonZero" | ||||||
|  |             android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" | ||||||
|  |             android:strokeColor="#00000000" | ||||||
|  |             android:strokeWidth="1"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,74 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <vector | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:height="108dp" | ||||||
|  |         android:width="108dp" | ||||||
|  |         android:viewportHeight="108" | ||||||
|  |         android:viewportWidth="108"> | ||||||
|  |     <path android:fillColor="#008577" | ||||||
|  |           android:pathData="M0,0h108v108h-108z"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M9,0L9,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,0L19,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M29,0L29,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M39,0L39,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M49,0L49,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M59,0L59,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M69,0L69,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M79,0L79,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M89,0L89,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M99,0L99,108" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,9L108,9" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,19L108,19" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,29L108,29" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,39L108,39" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,49L108,49" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,59L108,59" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,69L108,69" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,79L108,79" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,89L108,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M0,99L108,99" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,29L89,29" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,39L89,39" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,49L89,49" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,59L89,59" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,69L89,69" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M19,79L89,79" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M29,19L29,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M39,19L39,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M49,19L49,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M59,19L59,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M69,19L69,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  |     <path android:fillColor="#00000000" android:pathData="M79,19L79,89" | ||||||
|  |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:width="24dp" | ||||||
|  |         android:height="24dp" | ||||||
|  |         android:viewportWidth="24.0" | ||||||
|  |         android:viewportHeight="24.0"> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:width="24dp" | ||||||
|  |         android:height="24dp" | ||||||
|  |         android:viewportWidth="24.0" | ||||||
|  |         android:viewportHeight="24.0"> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:width="24dp" | ||||||
|  |         android:height="24dp" | ||||||
|  |         android:viewportWidth="24.0" | ||||||
|  |         android:viewportHeight="24.0"> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:width="24dp" | ||||||
|  |         android:height="24dp" | ||||||
|  |         android:viewportWidth="24.0" | ||||||
|  |         android:viewportHeight="24.0"> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:width="24dp" | ||||||
|  |         android:height="24dp" | ||||||
|  |         android:viewportWidth="24.0" | ||||||
|  |         android:viewportHeight="24.0"> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         android:width="24dp" | ||||||
|  |         android:height="24dp" | ||||||
|  |         android:viewportWidth="24.0" | ||||||
|  |         android:viewportHeight="24.0"> | ||||||
|  |     <path | ||||||
|  |             android:fillColor="#FF000000" | ||||||
|  |             android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z"/> | ||||||
|  | </vector> | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |        android:shape="rectangle"> | ||||||
|  |     <gradient | ||||||
|  |             android:angle="135" | ||||||
|  |             android:centerColor="#009688" | ||||||
|  |             android:endColor="#00695C" | ||||||
|  |             android:startColor="#4DB6AC" | ||||||
|  |             android:type="linear"/> | ||||||
|  | </shape> | ||||||
| @ -0,0 +1,26 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.drawerlayout.widget.DrawerLayout | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |         xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |         android:id="@+id/drawer_layout" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:fitsSystemWindows="true" | ||||||
|  |         tools:openDrawer="start"> | ||||||
|  | 
 | ||||||
|  |     <include | ||||||
|  |             layout="@layout/app_bar_main" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="match_parent"/> | ||||||
|  | 
 | ||||||
|  |     <com.google.android.material.navigation.NavigationView | ||||||
|  |             android:id="@+id/nav_view" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="match_parent" | ||||||
|  |             android:layout_gravity="start" | ||||||
|  |             android:fitsSystemWindows="true" | ||||||
|  |             app:headerLayout="@layout/nav_header_main" | ||||||
|  |             app:menu="@menu/activity_main_drawer"/> | ||||||
|  | 
 | ||||||
|  | </androidx.drawerlayout.widget.DrawerLayout> | ||||||
| @ -0,0 +1,34 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.coordinatorlayout.widget.CoordinatorLayout | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |         xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         tools:context=".view.MainActivity"> | ||||||
|  | 
 | ||||||
|  |     <com.google.android.material.appbar.AppBarLayout | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:theme="@style/AppTheme.AppBarOverlay"> | ||||||
|  | 
 | ||||||
|  |         <androidx.appcompat.widget.Toolbar | ||||||
|  |                 android:id="@+id/toolbar" | ||||||
|  |                 android:layout_width="match_parent" | ||||||
|  |                 android:layout_height="?attr/actionBarSize" | ||||||
|  |                 android:background="?attr/colorPrimary" | ||||||
|  |                 app:popupTheme="@style/AppTheme.PopupOverlay"/> | ||||||
|  | 
 | ||||||
|  |     </com.google.android.material.appbar.AppBarLayout> | ||||||
|  | 
 | ||||||
|  |     <include layout="@layout/content_main"/> | ||||||
|  | 
 | ||||||
|  |     <com.google.android.material.floatingactionbutton.FloatingActionButton | ||||||
|  |             android:id="@+id/fab" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_gravity="bottom|end" | ||||||
|  |             android:layout_margin="@dimen/fab_margin" | ||||||
|  |             app:srcCompat="@android:drawable/ic_dialog_email"/> | ||||||
|  | 
 | ||||||
|  | </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||||||
|  |         tools:showIn="@layout/app_bar_main" | ||||||
|  |         tools:context=".view.MainActivity"> | ||||||
|  | 
 | ||||||
|  |     <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="Hello World!" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintLeft_toLeftOf="parent" | ||||||
|  |             app:layout_constraintRight_toRightOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent"/> | ||||||
|  | 
 | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
| @ -0,0 +1,37 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="@dimen/nav_header_height" | ||||||
|  |         android:background="@drawable/side_nav_bar" | ||||||
|  |         android:paddingBottom="@dimen/activity_vertical_margin" | ||||||
|  |         android:paddingLeft="@dimen/activity_horizontal_margin" | ||||||
|  |         android:paddingRight="@dimen/activity_horizontal_margin" | ||||||
|  |         android:paddingTop="@dimen/activity_vertical_margin" | ||||||
|  |         android:theme="@style/ThemeOverlay.AppCompat.Dark" | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:gravity="bottom"> | ||||||
|  | 
 | ||||||
|  |     <ImageView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:paddingTop="@dimen/nav_header_vertical_spacing" | ||||||
|  |             app:srcCompat="@mipmap/ic_launcher_round" | ||||||
|  |             android:contentDescription="@string/nav_header_desc" | ||||||
|  |             android:id="@+id/imageView"/> | ||||||
|  | 
 | ||||||
|  |     <TextView | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:paddingTop="@dimen/nav_header_vertical_spacing" | ||||||
|  |             android:text="@string/nav_header_title" | ||||||
|  |             android:textAppearance="@style/TextAppearance.AppCompat.Body1"/> | ||||||
|  | 
 | ||||||
|  |     <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="@string/nav_header_subtitle" | ||||||
|  |             android:id="@+id/textView"/> | ||||||
|  | 
 | ||||||
|  | </LinearLayout> | ||||||
| @ -0,0 +1,38 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |       xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |       tools:showIn="navigation_view"> | ||||||
|  | 
 | ||||||
|  |     <group android:checkableBehavior="single"> | ||||||
|  |         <item | ||||||
|  |                 android:id="@+id/nav_home" | ||||||
|  |                 android:icon="@drawable/ic_menu_camera" | ||||||
|  |                 android:title="@string/menu_home"/> | ||||||
|  |         <item | ||||||
|  |                 android:id="@+id/nav_gallery" | ||||||
|  |                 android:icon="@drawable/ic_menu_gallery" | ||||||
|  |                 android:title="@string/menu_gallery"/> | ||||||
|  |         <item | ||||||
|  |                 android:id="@+id/nav_slideshow" | ||||||
|  |                 android:icon="@drawable/ic_menu_slideshow" | ||||||
|  |                 android:title="@string/menu_slideshow"/> | ||||||
|  |         <item | ||||||
|  |                 android:id="@+id/nav_tools" | ||||||
|  |                 android:icon="@drawable/ic_menu_manage" | ||||||
|  |                 android:title="@string/menu_tools"/> | ||||||
|  |     </group> | ||||||
|  | 
 | ||||||
|  |     <item android:title="Communicate"> | ||||||
|  |         <menu> | ||||||
|  |             <item | ||||||
|  |                     android:id="@+id/nav_share" | ||||||
|  |                     android:icon="@drawable/ic_menu_share" | ||||||
|  |                     android:title="@string/menu_share"/> | ||||||
|  |             <item | ||||||
|  |                     android:id="@+id/nav_send" | ||||||
|  |                     android:icon="@drawable/ic_menu_send" | ||||||
|  |                     android:title="@string/menu_send"/> | ||||||
|  |         </menu> | ||||||
|  |     </item> | ||||||
|  | 
 | ||||||
|  | </menu> | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |       xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||||
|  |     <item android:id="@+id/action_settings" | ||||||
|  |           android:title="@string/action_settings" | ||||||
|  |           android:orderInCategory="100" | ||||||
|  |           app:showAsAction="never"/> | ||||||
|  | </menu> | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <background android:drawable="@drawable/ic_launcher_background"/> | ||||||
|  |     <foreground android:drawable="@drawable/ic_launcher_foreground"/> | ||||||
|  | </adaptive-icon> | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <background android:drawable="@drawable/ic_launcher_background"/> | ||||||
|  |     <foreground android:drawable="@drawable/ic_launcher_foreground"/> | ||||||
|  | </adaptive-icon> | ||||||
| After Width: | Height: | Size: 2.9 KiB | 
| After Width: | Height: | Size: 4.8 KiB | 
| After Width: | Height: | Size: 2.0 KiB | 
| After Width: | Height: | Size: 2.7 KiB | 
| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 6.7 KiB | 
| After Width: | Height: | Size: 6.2 KiB | 
| After Width: | Height: | Size: 10 KiB | 
| After Width: | Height: | Size: 8.9 KiB | 
| After Width: | Height: | Size: 15 KiB | 
| @ -0,0 +1,7 @@ | |||||||
|  | <resources> | ||||||
|  |     <style name="AppTheme.NoActionBar"> | ||||||
|  |         <item name="windowActionBar">false</item> | ||||||
|  |         <item name="windowNoTitle">true</item> | ||||||
|  |         <item name="android:statusBarColor">@android:color/transparent</item> | ||||||
|  |     </style> | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  |     <color name="colorPrimary">#008577</color> | ||||||
|  |     <color name="colorPrimaryDark">#00574B</color> | ||||||
|  |     <color name="colorAccent">#D81B60</color> | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | <resources> | ||||||
|  |     <!-- Default screen margins, per the Android Design guidelines. --> | ||||||
|  |     <dimen name="activity_horizontal_margin">16dp</dimen> | ||||||
|  |     <dimen name="activity_vertical_margin">16dp</dimen> | ||||||
|  |     <dimen name="nav_header_vertical_spacing">8dp</dimen> | ||||||
|  |     <dimen name="nav_header_height">176dp</dimen> | ||||||
|  |     <dimen name="fab_margin">16dp</dimen> | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,16 @@ | |||||||
|  | <resources> | ||||||
|  |     <string name="app_name">阅读</string> | ||||||
|  |     <string name="navigation_drawer_open">Open navigation drawer</string> | ||||||
|  |     <string name="navigation_drawer_close">Close navigation drawer</string> | ||||||
|  |     <string name="nav_header_title">Android Studio</string> | ||||||
|  |     <string name="nav_header_subtitle">android.studio@android.com</string> | ||||||
|  |     <string name="nav_header_desc">Navigation header</string> | ||||||
|  |     <string name="action_settings">Settings</string> | ||||||
|  | 
 | ||||||
|  |     <string name="menu_home">Home</string> | ||||||
|  |     <string name="menu_gallery">Gallery</string> | ||||||
|  |     <string name="menu_slideshow">Slideshow</string> | ||||||
|  |     <string name="menu_tools">Tools</string> | ||||||
|  |     <string name="menu_share">Share</string> | ||||||
|  |     <string name="menu_send">Send</string> | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | <resources> | ||||||
|  | 
 | ||||||
|  |     <!-- Base application theme. --> | ||||||
|  |     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> | ||||||
|  |         <!-- Customize your theme here. --> | ||||||
|  |         <item name="colorPrimary">@color/colorPrimary</item> | ||||||
|  |         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||||
|  |         <item name="colorAccent">@color/colorAccent</item> | ||||||
|  |     </style> | ||||||
|  |     <style name="AppTheme.NoActionBar"> | ||||||
|  |         <item name="windowActionBar">false</item> | ||||||
|  |         <item name="windowNoTitle">true</item> | ||||||
|  |     </style> | ||||||
|  |     <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/> | ||||||
|  |     <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/> | ||||||
|  | 
 | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | package io.legado.book | ||||||
|  | 
 | ||||||
|  | import org.junit.Test | ||||||
|  | 
 | ||||||
|  | import org.junit.Assert.* | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Example local unit test, which will execute on the development machine (host). | ||||||
|  |  * | ||||||
|  |  * See [testing documentation](http://d.android.com/tools/testing). | ||||||
|  |  */ | ||||||
|  | class ExampleUnitTest { | ||||||
|  |     @Test | ||||||
|  |     fun addition_isCorrect() { | ||||||
|  |         assertEquals(4, 2 + 2) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,28 @@ | |||||||
|  | // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||||
|  | 
 | ||||||
|  | buildscript { | ||||||
|  |     ext.kotlin_version = '1.3.31' | ||||||
|  |     repositories { | ||||||
|  |         google() | ||||||
|  |         jcenter() | ||||||
|  |          | ||||||
|  |     } | ||||||
|  |     dependencies { | ||||||
|  |         classpath 'com.android.tools.build:gradle:3.4.1' | ||||||
|  |         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||||
|  |         // NOTE: Do not place your application dependencies here; they belong | ||||||
|  |         // in the individual module build.gradle files | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | allprojects { | ||||||
|  |     repositories { | ||||||
|  |         google() | ||||||
|  |         jcenter() | ||||||
|  |          | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | task clean(type: Delete) { | ||||||
|  |     delete rootProject.buildDir | ||||||
|  | } | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | # Project-wide Gradle settings. | ||||||
|  | # IDE (e.g. Android Studio) users: | ||||||
|  | # Gradle settings configured through the IDE *will override* | ||||||
|  | # any settings specified in this file. | ||||||
|  | # For more details on how to configure your build environment visit | ||||||
|  | # http://www.gradle.org/docs/current/userguide/build_environment.html | ||||||
|  | # Specifies the JVM arguments used for the daemon process. | ||||||
|  | # The setting is particularly useful for tweaking memory settings. | ||||||
|  | org.gradle.jvmargs=-Xmx1536m | ||||||
|  | # When configured, Gradle will run in incubating parallel mode. | ||||||
|  | # This option should only be used with decoupled projects. More details, visit | ||||||
|  | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||||
|  | # org.gradle.parallel=true | ||||||
|  | # AndroidX package structure to make it clearer which packages are bundled with the | ||||||
|  | # Android operating system, and which are packaged with your app's APK | ||||||
|  | # https://developer.android.com/topic/libraries/support-library/androidx-rn | ||||||
|  | android.useAndroidX=true | ||||||
|  | # Automatically convert third-party libraries to use AndroidX | ||||||
|  | android.enableJetifier=true | ||||||
|  | # Kotlin code style for this project: "official" or "obsolete": | ||||||
|  | kotlin.code.style=official | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | #Wed May 22 11:28:32 CST 2019 | ||||||
|  | distributionBase=GRADLE_USER_HOME | ||||||
|  | distributionPath=wrapper/dists | ||||||
|  | zipStoreBase=GRADLE_USER_HOME | ||||||
|  | zipStorePath=wrapper/dists | ||||||
|  | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip | ||||||
| @ -0,0 +1,172 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | 
 | ||||||
|  | ############################################################################## | ||||||
|  | ## | ||||||
|  | ##  Gradle start up script for UN*X | ||||||
|  | ## | ||||||
|  | ############################################################################## | ||||||
|  | 
 | ||||||
|  | # Attempt to set APP_HOME | ||||||
|  | # Resolve links: $0 may be a link | ||||||
|  | PRG="$0" | ||||||
|  | # Need this for relative symlinks. | ||||||
|  | while [ -h "$PRG" ] ; do | ||||||
|  |     ls=`ls -ld "$PRG"` | ||||||
|  |     link=`expr "$ls" : '.*-> \(.*\)$'` | ||||||
|  |     if expr "$link" : '/.*' > /dev/null; then | ||||||
|  |         PRG="$link" | ||||||
|  |     else | ||||||
|  |         PRG=`dirname "$PRG"`"/$link" | ||||||
|  |     fi | ||||||
|  | done | ||||||
|  | SAVED="`pwd`" | ||||||
|  | cd "`dirname \"$PRG\"`/" >/dev/null | ||||||
|  | APP_HOME="`pwd -P`" | ||||||
|  | cd "$SAVED" >/dev/null | ||||||
|  | 
 | ||||||
|  | APP_NAME="Gradle" | ||||||
|  | APP_BASE_NAME=`basename "$0"` | ||||||
|  | 
 | ||||||
|  | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||||
|  | DEFAULT_JVM_OPTS="" | ||||||
|  | 
 | ||||||
|  | # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||||
|  | MAX_FD="maximum" | ||||||
|  | 
 | ||||||
|  | warn () { | ||||||
|  |     echo "$*" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | die () { | ||||||
|  |     echo | ||||||
|  |     echo "$*" | ||||||
|  |     echo | ||||||
|  |     exit 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # OS specific support (must be 'true' or 'false'). | ||||||
|  | cygwin=false | ||||||
|  | msys=false | ||||||
|  | darwin=false | ||||||
|  | nonstop=false | ||||||
|  | case "`uname`" in | ||||||
|  |   CYGWIN* ) | ||||||
|  |     cygwin=true | ||||||
|  |     ;; | ||||||
|  |   Darwin* ) | ||||||
|  |     darwin=true | ||||||
|  |     ;; | ||||||
|  |   MINGW* ) | ||||||
|  |     msys=true | ||||||
|  |     ;; | ||||||
|  |   NONSTOP* ) | ||||||
|  |     nonstop=true | ||||||
|  |     ;; | ||||||
|  | esac | ||||||
|  | 
 | ||||||
|  | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||||
|  | 
 | ||||||
|  | # Determine the Java command to use to start the JVM. | ||||||
|  | if [ -n "$JAVA_HOME" ] ; then | ||||||
|  |     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||||||
|  |         # IBM's JDK on AIX uses strange locations for the executables | ||||||
|  |         JAVACMD="$JAVA_HOME/jre/sh/java" | ||||||
|  |     else | ||||||
|  |         JAVACMD="$JAVA_HOME/bin/java" | ||||||
|  |     fi | ||||||
|  |     if [ ! -x "$JAVACMD" ] ; then | ||||||
|  |         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||||||
|  | 
 | ||||||
|  | Please set the JAVA_HOME variable in your environment to match the | ||||||
|  | location of your Java installation." | ||||||
|  |     fi | ||||||
|  | else | ||||||
|  |     JAVACMD="java" | ||||||
|  |     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||||
|  | 
 | ||||||
|  | Please set the JAVA_HOME variable in your environment to match the | ||||||
|  | location of your Java installation." | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # Increase the maximum file descriptors if we can. | ||||||
|  | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | ||||||
|  |     MAX_FD_LIMIT=`ulimit -H -n` | ||||||
|  |     if [ $? -eq 0 ] ; then | ||||||
|  |         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||||||
|  |             MAX_FD="$MAX_FD_LIMIT" | ||||||
|  |         fi | ||||||
|  |         ulimit -n $MAX_FD | ||||||
|  |         if [ $? -ne 0 ] ; then | ||||||
|  |             warn "Could not set maximum file descriptor limit: $MAX_FD" | ||||||
|  |         fi | ||||||
|  |     else | ||||||
|  |         warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||||||
|  |     fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # For Darwin, add options to specify how the application appears in the dock | ||||||
|  | if $darwin; then | ||||||
|  |     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # For Cygwin, switch paths to Windows format before running java | ||||||
|  | if $cygwin ; then | ||||||
|  |     APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||||||
|  |     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||||||
|  |     JAVACMD=`cygpath --unix "$JAVACMD"` | ||||||
|  | 
 | ||||||
|  |     # We build the pattern for arguments to be converted via cygpath | ||||||
|  |     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||||||
|  |     SEP="" | ||||||
|  |     for dir in $ROOTDIRSRAW ; do | ||||||
|  |         ROOTDIRS="$ROOTDIRS$SEP$dir" | ||||||
|  |         SEP="|" | ||||||
|  |     done | ||||||
|  |     OURCYGPATTERN="(^($ROOTDIRS))" | ||||||
|  |     # Add a user-defined pattern to the cygpath arguments | ||||||
|  |     if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||||||
|  |         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||||||
|  |     fi | ||||||
|  |     # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||||||
|  |     i=0 | ||||||
|  |     for arg in "$@" ; do | ||||||
|  |         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||||||
|  |         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option | ||||||
|  | 
 | ||||||
|  |         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition | ||||||
|  |             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||||||
|  |         else | ||||||
|  |             eval `echo args$i`="\"$arg\"" | ||||||
|  |         fi | ||||||
|  |         i=$((i+1)) | ||||||
|  |     done | ||||||
|  |     case $i in | ||||||
|  |         (0) set -- ;; | ||||||
|  |         (1) set -- "$args0" ;; | ||||||
|  |         (2) set -- "$args0" "$args1" ;; | ||||||
|  |         (3) set -- "$args0" "$args1" "$args2" ;; | ||||||
|  |         (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||||||
|  |         (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||||||
|  |         (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||||||
|  |         (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||||||
|  |         (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||||||
|  |         (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||||||
|  |     esac | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # Escape application args | ||||||
|  | save () { | ||||||
|  |     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | ||||||
|  |     echo " " | ||||||
|  | } | ||||||
|  | APP_ARGS=$(save "$@") | ||||||
|  | 
 | ||||||
|  | # Collect all arguments for the java command, following the shell quoting and substitution rules | ||||||
|  | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | ||||||
|  | 
 | ||||||
|  | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | ||||||
|  | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | ||||||
|  |   cd "$(dirname "$0")" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | exec "$JAVACMD" "$@" | ||||||
| @ -0,0 +1,84 @@ | |||||||
|  | @if "%DEBUG%" == "" @echo off | ||||||
|  | @rem ########################################################################## | ||||||
|  | @rem | ||||||
|  | @rem  Gradle startup script for Windows | ||||||
|  | @rem | ||||||
|  | @rem ########################################################################## | ||||||
|  | 
 | ||||||
|  | @rem Set local scope for the variables with windows NT shell | ||||||
|  | if "%OS%"=="Windows_NT" setlocal | ||||||
|  | 
 | ||||||
|  | set DIRNAME=%~dp0 | ||||||
|  | if "%DIRNAME%" == "" set DIRNAME=. | ||||||
|  | set APP_BASE_NAME=%~n0 | ||||||
|  | set APP_HOME=%DIRNAME% | ||||||
|  | 
 | ||||||
|  | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||||
|  | set DEFAULT_JVM_OPTS= | ||||||
|  | 
 | ||||||
|  | @rem Find java.exe | ||||||
|  | if defined JAVA_HOME goto findJavaFromJavaHome | ||||||
|  | 
 | ||||||
|  | set JAVA_EXE=java.exe | ||||||
|  | %JAVA_EXE% -version >NUL 2>&1 | ||||||
|  | if "%ERRORLEVEL%" == "0" goto init | ||||||
|  | 
 | ||||||
|  | echo. | ||||||
|  | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||||
|  | echo. | ||||||
|  | echo Please set the JAVA_HOME variable in your environment to match the | ||||||
|  | echo location of your Java installation. | ||||||
|  | 
 | ||||||
|  | goto fail | ||||||
|  | 
 | ||||||
|  | :findJavaFromJavaHome | ||||||
|  | set JAVA_HOME=%JAVA_HOME:"=% | ||||||
|  | set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||||||
|  | 
 | ||||||
|  | if exist "%JAVA_EXE%" goto init | ||||||
|  | 
 | ||||||
|  | echo. | ||||||
|  | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||||||
|  | echo. | ||||||
|  | echo Please set the JAVA_HOME variable in your environment to match the | ||||||
|  | echo location of your Java installation. | ||||||
|  | 
 | ||||||
|  | goto fail | ||||||
|  | 
 | ||||||
|  | :init | ||||||
|  | @rem Get command-line arguments, handling Windows variants | ||||||
|  | 
 | ||||||
|  | if not "%OS%" == "Windows_NT" goto win9xME_args | ||||||
|  | 
 | ||||||
|  | :win9xME_args | ||||||
|  | @rem Slurp the command line arguments. | ||||||
|  | set CMD_LINE_ARGS= | ||||||
|  | set _SKIP=2 | ||||||
|  | 
 | ||||||
|  | :win9xME_args_slurp | ||||||
|  | if "x%~1" == "x" goto execute | ||||||
|  | 
 | ||||||
|  | set CMD_LINE_ARGS=%* | ||||||
|  | 
 | ||||||
|  | :execute | ||||||
|  | @rem Setup the command line | ||||||
|  | 
 | ||||||
|  | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||||
|  | 
 | ||||||
|  | @rem Execute Gradle | ||||||
|  | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | ||||||
|  | 
 | ||||||
|  | :end | ||||||
|  | @rem End local scope for the variables with windows NT shell | ||||||
|  | if "%ERRORLEVEL%"=="0" goto mainEnd | ||||||
|  | 
 | ||||||
|  | :fail | ||||||
|  | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||||||
|  | rem the _cmd.exe /c_ return code! | ||||||
|  | if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||||||
|  | exit /b 1 | ||||||
|  | 
 | ||||||
|  | :mainEnd | ||||||
|  | if "%OS%"=="Windows_NT" endlocal | ||||||
|  | 
 | ||||||
|  | :omega | ||||||
| @ -0,0 +1 @@ | |||||||
|  | include ':app' | ||||||