| @ -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' | ||||