├── .gitattributes ├── .github └── workflows │ ├── dependencies.yml │ └── release.yml ├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── umafan │ │ └── lib │ │ └── android │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── db │ │ │ └── main.db │ │ └── reader │ │ │ ├── css │ │ │ ├── element-ui.css │ │ │ ├── quill.core.css │ │ │ ├── quill.snow.css │ │ │ └── umalib.reader.css │ │ │ ├── index.html │ │ │ └── js │ │ │ ├── element-ui.js │ │ │ ├── index.js │ │ │ └── vue.js │ ├── java │ │ └── cn │ │ │ └── umafan │ │ │ └── lib │ │ │ └── android │ │ │ ├── model │ │ │ ├── DataBaseHandler.kt │ │ │ ├── MyApplication.kt │ │ │ ├── MyBaseActivity.kt │ │ │ ├── MyBaseViewModel.kt │ │ │ ├── PageSelectorViewModel.kt │ │ │ ├── SearchBean.kt │ │ │ └── db │ │ │ │ ├── ArtCreator.java │ │ │ │ ├── ArtInfo.java │ │ │ │ ├── Article.java │ │ │ │ ├── Creator.java │ │ │ │ ├── Dict.java │ │ │ │ ├── Rec.java │ │ │ │ ├── Tag.java │ │ │ │ └── Tagged.java │ │ │ ├── ui │ │ │ ├── MainIntroActivity.kt │ │ │ ├── UpdateLogActivity.kt │ │ │ ├── favorites │ │ │ │ ├── FavoritesFragment.kt │ │ │ │ └── FavoritesViewModel.kt │ │ │ ├── history │ │ │ │ ├── HistoryFragment.kt │ │ │ │ └── HistoryViewModel.kt │ │ │ ├── home │ │ │ │ ├── HomeFragment.kt │ │ │ │ ├── HomeViewModel.kt │ │ │ │ └── model │ │ │ │ │ ├── ArticleInfoItem.kt │ │ │ │ │ └── PageItem.kt │ │ │ ├── main │ │ │ │ ├── DatabaseCopyThread.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ └── model │ │ │ │ │ ├── CreatorSuggestionAdapter.kt │ │ │ │ │ ├── TagSelectedItem.kt │ │ │ │ │ └── TagSuggestionAdapter.kt │ │ │ ├── reader │ │ │ │ ├── ReaderActivity.kt │ │ │ │ ├── ReaderViewModel.kt │ │ │ │ └── model │ │ │ │ │ └── ReaderJSInterface.kt │ │ │ ├── recommend │ │ │ │ ├── RecommendFragment.kt │ │ │ │ ├── RecommendViewModel.kt │ │ │ │ └── model │ │ │ │ │ ├── RecCommentItem.kt │ │ │ │ │ ├── RecInfo.kt │ │ │ │ │ ├── RecJumpItem.kt │ │ │ │ │ └── RecTabItem.kt │ │ │ ├── setting │ │ │ │ ├── SettingActivity.kt │ │ │ │ └── SettingViewModel.kt │ │ │ └── thanks │ │ │ │ ├── ThanksFragment.kt │ │ │ │ └── ThanksViewModel.kt │ │ │ └── util │ │ │ ├── ContentUriUtil.java │ │ │ ├── DownloadUtil.kt │ │ │ ├── FavoriteArticleUtil.kt │ │ │ ├── HistoryUtil.kt │ │ │ ├── PageSizeUtil.kt │ │ │ ├── PinyinUtil.java │ │ │ ├── ReaderSettingUtil.kt │ │ │ ├── SettingUtil.kt │ │ │ ├── ZipUtil.kt │ │ │ └── network │ │ │ ├── ServiceCreator.kt │ │ │ ├── UpdateUtil.kt │ │ │ ├── model │ │ │ ├── UpdateBean.kt │ │ │ ├── UpdateButtonBean.kt │ │ │ └── UpdateInfoBean.kt │ │ │ └── service │ │ │ └── UpdateService.kt │ └── res │ │ ├── drawable-v24 │ │ ├── ic_github.xml │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── app_bg.png │ │ ├── app_database_intro.png │ │ ├── app_func_intro.jpg │ │ ├── app_tag_intro.jpg │ │ ├── app_view_intro.jpg │ │ ├── baseline_arrow_right_24.xml │ │ ├── baseline_help_outline_24.xml │ │ ├── baseline_menu_24.xml │ │ ├── baseline_menu_open_24.xml │ │ ├── baseline_volunteer_activism_24.xml │ │ ├── ic_baseline_article_24.xml │ │ ├── ic_baseline_cancel_24.xml │ │ ├── ic_baseline_chat_24.xml │ │ ├── ic_baseline_cloud_download_24.xml │ │ ├── ic_baseline_cloud_upload_24.xml │ │ ├── ic_baseline_collections_bookmark_24.xml │ │ ├── ic_baseline_color_lens_24.xml │ │ ├── ic_baseline_filter_alt_24.xml │ │ ├── ic_baseline_history_24.xml │ │ ├── ic_baseline_insert_photo_24.xml │ │ ├── ic_baseline_navigate_before_24.xml │ │ ├── ic_baseline_navigate_next_24.xml │ │ ├── ic_baseline_recommend_24.xml │ │ ├── ic_baseline_refresh_24.xml │ │ ├── ic_baseline_search_24.xml │ │ ├── ic_baseline_settings_24.xml │ │ ├── ic_baseline_star_24.xml │ │ ├── ic_baseline_star_outline_24.xml │ │ ├── ic_baseline_system_update_24.xml │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.xml │ │ ├── ic_lightbulb_24px.xml │ │ ├── index_bg.png │ │ ├── kitasan.jpg │ │ ├── normal_bg.png │ │ ├── side_nav_bar.xml │ │ └── tokaiteio.png │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_main_intro.xml │ │ ├── activity_reader.xml │ │ ├── activity_setting.xml │ │ ├── activity_update_log.xml │ │ ├── app_bar_main.xml │ │ ├── content_main.xml │ │ ├── dialog_loading_database.xml │ │ ├── dialog_page_selector.xml │ │ ├── dialog_reader_setting.xml │ │ ├── dialog_search_filter.xml │ │ ├── fragment_collections.xml │ │ ├── fragment_favorites.xml │ │ ├── fragment_histroy.xml │ │ ├── fragment_home.xml │ │ ├── fragment_recommend.xml │ │ ├── fragment_thanks.xml │ │ ├── item_article_card.xml │ │ ├── item_jump_button.xml │ │ ├── item_page_button.xml │ │ ├── item_rec_card.xml │ │ ├── item_rec_comment.xml │ │ ├── item_selected_tag.xml │ │ ├── item_tag_suggestion.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ └── main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── mobile_navigation.xml │ │ ├── values-land │ │ └── dimens.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values-w1240dp │ │ └── dimens.xml │ │ ├── values-w600dp │ │ └── dimens.xml │ │ ├── values │ │ ├── attr.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── shape.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ └── file_paths.xml │ └── test │ └── java │ └── cn │ └── umafan │ └── lib │ └── android │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preference ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── liangguo │ │ └── preference │ │ └── views │ │ ├── BaselineGridTextView.java │ │ └── CommonSettingView.kt │ └── res │ ├── drawable │ ├── ic_baseline_keyboard_arrow_right_24.xml │ ├── switch_thumb.xml │ └── switch_track.xml │ ├── layout │ └── item_common_preference.xml │ └── values │ ├── attrs_baseline_textview.xml │ ├── attrs_common_setting_view.xml │ ├── attrs_pref_common.xml │ ├── attrs_pref_list.xml │ ├── dimens.xml │ └── strings.xml └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | *.db filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/workflows/dependencies.yml: -------------------------------------------------------------------------------- 1 | name: gradle-dependency-detection 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | gradle-dependency-detection: 10 | runs-on: ubuntu-latest 11 | # The Dependency Submission API requires write permission 12 | permissions: 13 | contents: write 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Analyze dependencies 17 | uses: mikepenz/gradle-dependency-submission@v0.8.4 18 | with: 19 | gradle-build-module: |- 20 | :app 21 | :preference 22 | gradle-build-configuration: |- 23 | debugCompileClasspath -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: umalib-build-and-release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build-and-release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | lfs: 'true' 17 | - name: set up JDK 11 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '11' 21 | distribution: 'temurin' 22 | cache: gradle 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Assemble release 26 | run: ./gradlew :app:assembleRelease 27 | - name: ZipAlign & Sign android release 28 | id: sign_app 29 | uses: kevin-david/zipalign-sign-android-release@v1.1 30 | with: 31 | releaseDirectory: app/build/outputs/apk/release 32 | signingKeyBase64: ${{ secrets.JKS_FILE }} 33 | alias: ${{ secrets.KEY_ALIAS }} 34 | keyStorePassword: ${{ secrets.STORE_PASSWORD }} 35 | keyPassword: ${{ secrets.KEY_PASSWORD }} 36 | zipAlign: true 37 | - name: Move 38 | run: mv ${{ steps.sign_app.outputs.signedReleaseFile }} ./umalib-android-${{ github.ref_name }}.apk 39 | - name: Release 40 | uses: softprops/action-gh-release@v1 41 | with: 42 | tag_name: ${{ github.ref_name }} 43 | name: Uma Library Android ${{ github.ref_name }} 44 | body: built by Github Actions 45 | draft: false 46 | prerelease: false 47 | files: ./umalib-android-${{ github.ref_name }}.apk -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uma Library Android 2 | 3 | A library of doujin articles of _Umamusume: Pretty Derby_ 4 | 5 | ## How to Contribute 6 | 7 | Forks and sends Pull Request to [UmaLibAndroid](https://github.com/umalib/UmaLibAndroid) 8 | 9 | ## Support 10 | 11 | Publishing post (Chinese): https://bbs.nga.cn/read.php?tid=33041357 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.greenrobot.greendao' 4 | id 'org.jetbrains.kotlin.android' 5 | } 6 | 7 | android { 8 | compileSdk 31 9 | 10 | defaultConfig { 11 | applicationId "cn.umafan.lib.android" 12 | minSdk 26 13 | targetSdk 31 14 | versionCode 2 15 | versionName "3.0.2" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | 19 | buildFeatures { 20 | dataBinding = true 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | kotlinOptions { 35 | jvmTarget = '1.8' 36 | } 37 | buildFeatures { 38 | viewBinding true 39 | } 40 | } 41 | 42 | 43 | dependencies { 44 | implementation project(path: ':preference') 45 | 46 | implementation 'androidx.appcompat:appcompat:1.4.1' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 48 | implementation 'androidx.core:core-ktx:1.7.0' 49 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 50 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' 51 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 52 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' 53 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' 54 | implementation 'com.github.angcyo:DslAdapter:4.1.0' 55 | implementation 'com.github.Ferfalk:SimpleSearchView:0.2.0' 56 | implementation 'com.google.android.material:material:1.6.0-alpha02' 57 | implementation 'org.greenrobot:greendao:3.3.0' 58 | 59 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 60 | implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.3' 61 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 62 | 63 | implementation 'com.heinrichreimersoftware:material-intro:2.0.0' 64 | implementation 'com.github.tiagohm.MarkdownView:library:0.19.0' 65 | implementation 'com.github.getActivity:XXPermissions:16.0' 66 | 67 | implementation 'com.github.zzz40500:android-shapeLoadingView:1.0.3.2' 68 | implementation 'com.github.ldh-star:AndroidKit:0.1.5' 69 | implementation 'com.github.open-android:BridgeWebView:v1.0' 70 | implementation 'com.belerweb:pinyin4j:2.5.0' 71 | 72 | // 文件下载 73 | implementation "com.tonyodev.fetch2:fetch2:3.0.12" 74 | // 消息通知 75 | implementation "io.karn:notify:1.4.0" 76 | // 解压文件 77 | 78 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 79 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 80 | 81 | testImplementation 'junit:junit:4.13.2' 82 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/umafan/lib/android/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("cn.umafan.lib.android", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 24 | 28 | 32 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 53 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/assets/db/main.db: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e0cef2348d6c73247b30a6079a9e8d19bc0d20b3aeff1728d0644061f1cdf0f4 3 | size 32768 4 | -------------------------------------------------------------------------------- /app/src/main/assets/reader/css/umalib.reader.css: -------------------------------------------------------------------------------- 1 | div.theme-nga, 2 | div.theme-nga div.ql-editor, 3 | div.theme-nga div.el-descriptions .el-descriptions-item__cell { 4 | color: #10273f; 5 | background-color: #f5e8cb; 6 | } 7 | 8 | div.theme-cyan, 9 | div.theme-cyan div.ql-editor, 10 | div.theme-cyan div.el-descriptions .el-descriptions-item__cell { 11 | color: #10273f; 12 | background-color: #d3dedb; 13 | } 14 | 15 | div.theme-teio, 16 | div.theme-teio div.ql-editor, 17 | div.theme-teio div.el-descriptions .el-descriptions-item__cell { 18 | color: navy; 19 | background-color: #d0daff; 20 | } 21 | 22 | div.theme-purple, 23 | div.theme-purple div.ql-editor, 24 | div.theme-purple div.el-descriptions .el-descriptions-item__cell { 25 | color: #10273f; 26 | background-color: #ded1d4; 27 | } 28 | 29 | div.theme-black, 30 | div.theme-black div.ql-editor, 31 | div.theme-black div.el-descriptions .el-descriptions-item__cell { 32 | color: #ddedf5; 33 | background-color: #34312e; 34 | } 35 | 36 | div.theme-green, 37 | div.theme-green div.ql-editor, 38 | div.theme-green div.el-descriptions .el-descriptions-item__cell { 39 | color: black; 40 | background-color: #dde9dd; 41 | } 42 | 43 | div.theme-exhentai, 44 | div.theme-exhentai div.ql-editor, 45 | div.theme-exhentai div.el-descriptions .el-descriptions-item__cell { 46 | color: #f1f1f1; 47 | background-color: #3e424a; 48 | } 49 | 50 | div.theme-porn, 51 | div.theme-porn div.ql-editor, 52 | div.theme-porn div.el-descriptions .el-descriptions-item__cell { 53 | color: #e4e4e4; 54 | background-color: black; 55 | } 56 | 57 | div.el-descriptions .el-descriptions-item__label { 58 | min-width: 50px; 59 | } 60 | 61 | div.ql-editor img { 62 | max-width: 100%; 63 | } 64 | 65 | div.small-font div.ql-editor span.annotation, 66 | div.small-font div.ql-editor blockquote { 67 | font-size: 0.75em; 68 | } 69 | 70 | div.normal-font div.ql-editor h1 { 71 | font-size: 2.5em; 72 | } 73 | 74 | div.normal-font div.ql-editor h2 { 75 | font-size: 2.25em; 76 | } 77 | 78 | div.normal-font div.ql-editor p, 79 | div.normal-font div.ql-editor li { 80 | font-size: 1.5em; 81 | } 82 | 83 | div.normal-font div.ql-editor span.ql-size-large { 84 | font-size: 2em; 85 | } 86 | 87 | div.normal-font div.ql-editor span.ql-size-small, 88 | div.normal-font div.ql-editor span.annotation, 89 | div.normal-font div.ql-editor blockquote { 90 | font-size: 1em; 91 | } 92 | 93 | div.large-font div.ql-editor h1 { 94 | font-size: 3.75em; 95 | } 96 | 97 | div.large-font div.ql-editor h2 { 98 | font-size: 3.375em; 99 | } 100 | 101 | div.large-font div.ql-editor p, 102 | div.large-font div.ql-editor li { 103 | font-size: 2.25em; 104 | } 105 | 106 | div.large-font div.ql-editor span.ql-size-large { 107 | font-size: 3em; 108 | } 109 | 110 | div.large-font div.ql-editor span.ql-size-small, 111 | div.large-font div.ql-editor span.annotation, 112 | div.large-font div.ql-editor blockquote { 113 | font-size: 1.5em; 114 | } 115 | 116 | div.wider-space div.ql-editor p, 117 | div.wider-space div.ql-editor h1, 118 | div.wider-space div.ql-editor h2 { 119 | margin-bottom: 5px; 120 | } 121 | 122 | div.wider-space div.ql-editor p, 123 | div.wider-space div.ql-editor h1, 124 | div.wider-space div.ql-editor h2 { 125 | margin-bottom: 10px; 126 | } 127 | 128 | div.wider-space div.ql-editor p, 129 | div.wider-space div.ql-editor h1, 130 | div.wider-space div.ql-editor h2 { 131 | margin-bottom: 15px; 132 | } 133 | 134 | span + span.annotation { 135 | display: none; 136 | } 137 | 138 | span.key + span.annotation { 139 | display: block; 140 | border-left: 4px solid #ccc; 141 | margin-bottom: 5px; 142 | margin-top: 5px; 143 | padding-left: 16px; 144 | } -------------------------------------------------------------------------------- /app/src/main/assets/reader/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | {{ article.name }} 15 | {{ article.author }} 16 | 17 | {{ article.translator }} 18 | 19 | {{ article.time }} 20 | 21 | 22 | {{ tag.name }} 23 | 24 | 25 | 26 |
27 |
28 | {{ src }} 29 |
30 |
31 |
32 | {{ article.note }} 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 99 | -------------------------------------------------------------------------------- /app/src/main/assets/reader/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 统一管理js调用安卓方法 3 | * @param method 方法名 4 | * @param params 参数 数组格式 5 | */ 6 | var callAndroidMethod = function(method, params){ 7 | window.jsInterface.invokeMethod(method, [JSON.stringify(params)]);//json对象转成字符串,再转成字符串数组 8 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/DataBaseHandler.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import android.os.Message 6 | 7 | /** 8 | * 自定义handler模板,每个数据库的操作都通过此handler构建 9 | */ 10 | class DataBaseHandler( 11 | private val activity: MyBaseActivity, 12 | private val unit: (Message) -> Unit 13 | ) : Handler(Looper.getMainLooper()) { 14 | override fun handleMessage(msg: Message) { 15 | super.handleMessage(msg) 16 | when (msg.what) { 17 | // 若数据库在加载中,则展示进度条 18 | MyApplication.DATABASE_LOADING -> { 19 | activity.dataBaseLoadingDialog(msg.obj as Double) 20 | } 21 | MyApplication.DATABASE_LOADED -> { 22 | activity.dataBaseLoadingDialog(100.0) 23 | try { 24 | unit(msg) 25 | } catch (e: Exception) { 26 | e.printStackTrace() 27 | } 28 | activity.shapeLoadingDialog?.dialog?.hide() 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.content.Context 6 | 7 | class MyApplication : Application() { 8 | companion object { 9 | @SuppressLint("StaticFieldLeak") 10 | lateinit var context: Context 11 | const val DATABASE_LOADING = 0 12 | const val DATABASE_LOADED = 1 13 | 14 | fun getVersion(): VersionBean { 15 | val v = context.packageManager.getPackageInfo(context.packageName, 0) 16 | return VersionBean( 17 | v.versionCode, 18 | v.versionName 19 | ) 20 | 21 | } 22 | } 23 | 24 | override fun onCreate() { 25 | super.onCreate() 26 | context = applicationContext 27 | } 28 | 29 | } 30 | 31 | data class VersionBean( 32 | val code: Int, 33 | val name: String 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/MyBaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import cn.umafan.lib.android.util.network.UpdateUtil 8 | import cn.umafan.lib.android.util.network.model.UpdateBean 9 | import com.liangguo.androidkit.app.ToastUtil 10 | import kotlinx.coroutines.launch 11 | import java.io.BufferedReader 12 | import java.io.BufferedWriter 13 | import java.io.FileReader 14 | import java.io.FileWriter 15 | 16 | 17 | class MyBaseViewModel : ViewModel() { 18 | val updateInfo = MutableLiveData() 19 | 20 | companion object { 21 | private val verFile = MyApplication.context.getDatabasePath("version") 22 | private var dbVersion = 0 23 | 24 | fun getDbVersion(): Int { 25 | verFile.parentFile?.mkdirs() 26 | var flag = !verFile.exists() 27 | if (dbVersion == 0) { 28 | try { 29 | val br = BufferedReader(FileReader(verFile)) 30 | dbVersion = Integer.parseInt(br.readLine()) 31 | br.close() 32 | } catch (e: Exception) { 33 | Log.e("MyBaseViewModel", e.message!!) 34 | flag = true 35 | } 36 | } 37 | if (flag) { 38 | setDbVersion(dbVersion) 39 | } 40 | return dbVersion 41 | } 42 | 43 | fun setDbVersion(version: Int) { 44 | dbVersion = version 45 | val bw = BufferedWriter(FileWriter(verFile)) 46 | bw.write(version.toString()) 47 | bw.close() 48 | Log.d("DBVersion", "set db version $dbVersion") 49 | } 50 | } 51 | 52 | fun getUpdate(activity: MyBaseActivity, initiative: Boolean) { 53 | viewModelScope.launch { 54 | UpdateUtil.getUpdate().apply { 55 | if (initiative) activity.shapeLoadingDialog?.dialog?.hide() 56 | if (null != this) { 57 | Log.d("DBVersion", getDbVersion().toString()) 58 | this.show = 59 | if (this.currentVersionName > MyApplication.getVersion().name) 2 60 | else if (this.currentDb > getDbVersion()) 1 61 | else 0 62 | this.initiative = initiative 63 | updateInfo.value = this 64 | } else { 65 | ToastUtil.error("获取更新失败,请检查网络!") 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/PageSelectorViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.google.android.material.card.MaterialCardView 6 | 7 | 8 | open class PageSelectorViewModel : ViewModel() { 9 | var checkedList = mutableListOf() 10 | 11 | val checkedButton = MutableLiveData() 12 | 13 | val selectedPage = MutableLiveData(1) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/SearchBean.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model 2 | 3 | import cn.umafan.lib.android.model.db.Tag 4 | import java.io.Serializable 5 | 6 | data class SearchBean( 7 | var keyword: String? = "", 8 | var tags: MutableSet = mutableSetOf(), 9 | var creator: String? = "", 10 | var exceptedTags: MutableSet = mutableSetOf(), 11 | var isRandom: Boolean = false 12 | ) : Serializable 13 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/db/ArtCreator.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model.db; 2 | 3 | import org.greenrobot.greendao.annotation.Entity; 4 | import org.greenrobot.greendao.annotation.Id; 5 | import org.greenrobot.greendao.annotation.Property; 6 | import org.greenrobot.greendao.annotation.Generated; 7 | 8 | @Entity(createInDb = false, nameInDb = "article") 9 | public class ArtCreator { 10 | @Id 11 | @Property(nameInDb = "id") 12 | private Long id; 13 | 14 | @Property(nameInDb = "author") 15 | private String author; 16 | @Property(nameInDb = "translator") 17 | private String translator; 18 | 19 | @Generated(hash = 1069894656) 20 | public ArtCreator(Long id, String author, String translator) { 21 | this.id = id; 22 | this.author = author; 23 | this.translator = translator; 24 | } 25 | 26 | @Generated(hash = 2043771935) 27 | public ArtCreator() { 28 | } 29 | 30 | public Long getId() { 31 | return this.id; 32 | } 33 | 34 | public void setId(Long id) { 35 | this.id = id; 36 | } 37 | 38 | public String getAuthor() { 39 | return this.author; 40 | } 41 | 42 | public void setAuthor(String author) { 43 | this.author = author; 44 | } 45 | 46 | public String getTranslator() { 47 | return this.translator; 48 | } 49 | 50 | public void setTranslator(String translator) { 51 | this.translator = translator; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/db/Creator.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model.db; 2 | 3 | import org.greenrobot.greendao.annotation.Entity; 4 | import org.greenrobot.greendao.annotation.Generated; 5 | import org.greenrobot.greendao.annotation.Id; 6 | import org.greenrobot.greendao.annotation.Property; 7 | 8 | @Entity(createInDb = false) 9 | public class Creator { 10 | @Id 11 | @Property(nameInDb = "id") 12 | private Long id; 13 | 14 | @Property(nameInDb = "names") 15 | private String names; 16 | 17 | @Generated(hash = 1493267542) 18 | public Creator(Long id, String names) { 19 | this.id = id; 20 | this.names = names; 21 | } 22 | 23 | @Generated(hash = 908439796) 24 | public Creator() { 25 | } 26 | 27 | public Long getId() { 28 | return this.id; 29 | } 30 | 31 | public void setId(Long id) { 32 | this.id = id; 33 | } 34 | 35 | public String getNames() { 36 | return this.names; 37 | } 38 | 39 | public void setNames(String names) { 40 | this.names = names; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/db/Dict.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model.db; 2 | 3 | import org.greenrobot.greendao.annotation.Entity; 4 | import org.greenrobot.greendao.annotation.Id; 5 | import org.greenrobot.greendao.annotation.Property; 6 | 7 | import java.io.Serializable; 8 | import org.greenrobot.greendao.annotation.Generated; 9 | 10 | @Entity(createInDb = false) 11 | public class Dict implements Serializable { 12 | private static final long serialVersionUID = 1L; 13 | 14 | @Id 15 | @Property(nameInDb = "id") 16 | private Long id; 17 | 18 | @Property(nameInDb = "class") 19 | private String classType; 20 | 21 | @Property(nameInDb = "desc") 22 | private String desc; 23 | 24 | @Property(nameInDb = "key") 25 | private String key; 26 | 27 | @Property(nameInDb = "refId") 28 | private Integer refId; 29 | 30 | @Property(nameInDb = "related") 31 | private String related; 32 | 33 | @Property(nameInDb = "relatedId") 34 | private Integer relatedId; 35 | 36 | @Generated(hash = 1019137228) 37 | public Dict(Long id, String classType, String desc, String key, Integer refId, 38 | String related, Integer relatedId) { 39 | this.id = id; 40 | this.classType = classType; 41 | this.desc = desc; 42 | this.key = key; 43 | this.refId = refId; 44 | this.related = related; 45 | this.relatedId = relatedId; 46 | } 47 | 48 | @Generated(hash = 1138334630) 49 | public Dict() { 50 | } 51 | 52 | public Long getId() { 53 | return this.id; 54 | } 55 | 56 | public void setId(Long id) { 57 | this.id = id; 58 | } 59 | 60 | public String getClassType() { 61 | return this.classType; 62 | } 63 | 64 | public void setClassType(String classType) { 65 | this.classType = classType; 66 | } 67 | 68 | public String getDesc() { 69 | return this.desc; 70 | } 71 | 72 | public void setDesc(String desc) { 73 | this.desc = desc; 74 | } 75 | 76 | public String getKey() { 77 | return this.key; 78 | } 79 | 80 | public void setKey(String key) { 81 | this.key = key; 82 | } 83 | 84 | public Integer getRefId() { 85 | return this.refId; 86 | } 87 | 88 | public void setRefId(Integer refId) { 89 | this.refId = refId; 90 | } 91 | 92 | public String getRelated() { 93 | return this.related; 94 | } 95 | 96 | public void setRelated(String related) { 97 | this.related = related; 98 | } 99 | 100 | public Integer getRelatedId() { 101 | return this.relatedId; 102 | } 103 | 104 | public void setRelatedId(Integer relatedId) { 105 | this.relatedId = relatedId; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/db/Rec.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model.db; 2 | 3 | import org.greenrobot.greendao.annotation.Entity; 4 | import org.greenrobot.greendao.annotation.Id; 5 | import org.greenrobot.greendao.annotation.Property; 6 | 7 | import java.io.Serializable; 8 | import org.greenrobot.greendao.annotation.Generated; 9 | 10 | @Entity(createInDb = false) 11 | public class Rec implements Serializable { 12 | private static final long serialVersionUID = 1L; 13 | 14 | @Id 15 | @Property(nameInDb = "id") 16 | private Long id; 17 | 18 | @Property(nameInDb = "refId") 19 | private Long refId; 20 | 21 | @Property(nameInDb = "title") 22 | private String title; 23 | 24 | @Property(nameInDb = "others") 25 | private String others; 26 | 27 | @Property(nameInDb = "type") 28 | private Integer type; 29 | 30 | @Property(nameInDb = "r") 31 | private Integer r; 32 | 33 | @Property(nameInDb = "name") 34 | private String name; 35 | 36 | @Property(nameInDb = "reason") 37 | private String reason; 38 | 39 | @Generated(hash = 1774498343) 40 | public Rec(Long id, Long refId, String title, String others, Integer type, 41 | Integer r, String name, String reason) { 42 | this.id = id; 43 | this.refId = refId; 44 | this.title = title; 45 | this.others = others; 46 | this.type = type; 47 | this.r = r; 48 | this.name = name; 49 | this.reason = reason; 50 | } 51 | 52 | @Generated(hash = 424129248) 53 | public Rec() { 54 | } 55 | 56 | public Long getId() { 57 | return this.id; 58 | } 59 | 60 | public void setId(Long id) { 61 | this.id = id; 62 | } 63 | 64 | public Long getRefId() { 65 | return this.refId; 66 | } 67 | 68 | public void setRefId(Long refId) { 69 | this.refId = refId; 70 | } 71 | 72 | public String getTitle() { 73 | return this.title; 74 | } 75 | 76 | public void setTitle(String title) { 77 | this.title = title; 78 | } 79 | 80 | public String getOthers() { 81 | return this.others; 82 | } 83 | 84 | public void setOthers(String others) { 85 | this.others = others; 86 | } 87 | 88 | public Integer getType() { 89 | return this.type; 90 | } 91 | 92 | public void setType(Integer type) { 93 | this.type = type; 94 | } 95 | 96 | public Integer getR() { 97 | return this.r; 98 | } 99 | 100 | public void setR(Integer r) { 101 | this.r = r; 102 | } 103 | 104 | public String getName() { 105 | return this.name; 106 | } 107 | 108 | public void setName(String name) { 109 | this.name = name; 110 | } 111 | 112 | public String getReason() { 113 | return this.reason; 114 | } 115 | 116 | public void setReason(String reason) { 117 | this.reason = reason; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/model/db/Tag.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.model.db; 2 | 3 | import org.greenrobot.greendao.DaoException; 4 | import org.greenrobot.greendao.annotation.Entity; 5 | import org.greenrobot.greendao.annotation.Generated; 6 | import org.greenrobot.greendao.annotation.Id; 7 | import org.greenrobot.greendao.annotation.Property; 8 | import org.greenrobot.greendao.annotation.ToMany; 9 | 10 | import java.io.Serializable; 11 | import java.util.List; 12 | 13 | @Entity(createInDb = false) 14 | public class Tag implements Serializable { 15 | private static final long serialVersionUID = 1L; 16 | @Id 17 | @Property(nameInDb = "id") 18 | private Long id; 19 | 20 | @Property(nameInDb = "name") 21 | private String name; 22 | @Property(nameInDb = "type") 23 | private Integer type; 24 | @Property(nameInDb = "cover") 25 | private String cover; 26 | @Property(nameInDb = "description") 27 | private String description; 28 | 29 | @ToMany(referencedJoinProperty = "tagId") 30 | private List taggedList; 31 | /** 32 | * Used to resolve relations 33 | */ 34 | @Generated(hash = 2040040024) 35 | private transient DaoSession daoSession; 36 | /** 37 | * Used for active entity operations. 38 | */ 39 | @Generated(hash = 2076396065) 40 | private transient TagDao myDao; 41 | 42 | @Generated(hash = 1132560429) 43 | public Tag(Long id, String name, Integer type, String cover, 44 | String description) { 45 | this.id = id; 46 | this.name = name; 47 | this.type = type; 48 | this.cover = cover; 49 | this.description = description; 50 | } 51 | 52 | @Generated(hash = 1605720318) 53 | public Tag() { 54 | } 55 | 56 | public Long getId() { 57 | return this.id; 58 | } 59 | 60 | public void setId(Long id) { 61 | this.id = id; 62 | } 63 | 64 | public String getName() { 65 | return this.name; 66 | } 67 | 68 | public void setName(String name) { 69 | this.name = name; 70 | } 71 | 72 | public Integer getType() { 73 | return this.type; 74 | } 75 | 76 | public void setType(Integer type) { 77 | this.type = type; 78 | } 79 | 80 | public String getCover() { 81 | return this.cover; 82 | } 83 | 84 | public void setCover(String cover) { 85 | this.cover = cover; 86 | } 87 | 88 | public String getDescription() { 89 | return this.description; 90 | } 91 | 92 | public void setDescription(String description) { 93 | this.description = description; 94 | } 95 | 96 | /** 97 | * To-many relationship, resolved on first access (and after reset). 98 | * Changes to to-many relations are not persisted, make changes to the target entity. 99 | */ 100 | @Generated(hash = 273028975) 101 | public List getTaggedList() { 102 | if (taggedList == null) { 103 | final DaoSession daoSession = this.daoSession; 104 | if (daoSession == null) { 105 | throw new DaoException("Entity is detached from DAO context"); 106 | } 107 | TaggedDao targetDao = daoSession.getTaggedDao(); 108 | List taggedListNew = targetDao._queryTag_TaggedList(id); 109 | synchronized (this) { 110 | if (taggedList == null) { 111 | taggedList = taggedListNew; 112 | } 113 | } 114 | } 115 | return taggedList; 116 | } 117 | 118 | /** 119 | * Resets a to-many relationship, making the next get call to query for a fresh result. 120 | */ 121 | @Generated(hash = 300101849) 122 | public synchronized void resetTaggedList() { 123 | taggedList = null; 124 | } 125 | 126 | /** 127 | * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. 128 | * Entity must attached to an entity context. 129 | */ 130 | @Generated(hash = 128553479) 131 | public void delete() { 132 | if (myDao == null) { 133 | throw new DaoException("Entity is detached from DAO context"); 134 | } 135 | myDao.delete(this); 136 | } 137 | 138 | /** 139 | * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. 140 | * Entity must attached to an entity context. 141 | */ 142 | @Generated(hash = 1942392019) 143 | public void refresh() { 144 | if (myDao == null) { 145 | throw new DaoException("Entity is detached from DAO context"); 146 | } 147 | myDao.refresh(this); 148 | } 149 | 150 | /** 151 | * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. 152 | * Entity must attached to an entity context. 153 | */ 154 | @Generated(hash = 713229351) 155 | public void update() { 156 | if (myDao == null) { 157 | throw new DaoException("Entity is detached from DAO context"); 158 | } 159 | myDao.update(this); 160 | } 161 | 162 | /** called by internal mechanisms, do not call yourself. */ 163 | @Generated(hash = 441429822) 164 | public void __setDaoSession(DaoSession daoSession) { 165 | this.daoSession = daoSession; 166 | myDao = daoSession != null ? daoSession.getTagDao() : null; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/MainIntroActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui 2 | 3 | import android.os.Bundle 4 | import cn.umafan.lib.android.R 5 | import com.heinrichreimersoftware.materialintro.app.IntroActivity 6 | import com.heinrichreimersoftware.materialintro.slide.SimpleSlide 7 | 8 | 9 | class MainIntroActivity : IntroActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | isFullscreen = true 12 | super.onCreate(savedInstanceState) 13 | 14 | addSlide( 15 | SimpleSlide.Builder() 16 | .title(R.string.app_name) 17 | .description(R.string.app_description) 18 | .image(R.drawable.ic_launcher) 19 | .background(R.color.white) 20 | .backgroundDark(R.color.black) 21 | .scrollable(false) 22 | .build() 23 | ) 24 | 25 | addSlide( 26 | SimpleSlide.Builder() 27 | .title(R.string.app_database_intro) 28 | .description(R.string.app_database_description) 29 | .image(R.drawable.app_database_intro) 30 | .background(R.color.blue_500) 31 | .backgroundDark(R.color.blue_500) 32 | .scrollable(false) 33 | .build() 34 | ) 35 | 36 | addSlide( 37 | SimpleSlide.Builder() 38 | .title(R.string.app_view_intro) 39 | .description(R.string.app_view_description) 40 | .image(R.drawable.app_view_intro) 41 | .background(R.color.purple_500) 42 | .backgroundDark(R.color.purple_500) 43 | .scrollable(false) 44 | .build() 45 | ) 46 | 47 | addSlide( 48 | SimpleSlide.Builder() 49 | .title(R.string.app_tag_intro) 50 | .description(R.string.app_tag_description) 51 | .image(R.drawable.app_tag_intro) 52 | .background(R.color.teal_200) 53 | .backgroundDark(R.color.teal_200) 54 | .scrollable(false) 55 | .build() 56 | ) 57 | 58 | addSlide( 59 | SimpleSlide.Builder() 60 | .title(R.string.app_func_intro) 61 | .description(R.string.app_func_description) 62 | .image(R.drawable.app_func_intro) 63 | .background(R.color.lightPurple) 64 | .backgroundDark(R.color.lightPurple) 65 | .scrollable(false) 66 | .build() 67 | ) 68 | } 69 | 70 | override fun onBackPressed() {} 71 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/UpdateLogActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui 2 | 3 | import android.os.Bundle 4 | import android.view.MenuItem 5 | import br.tiagohm.markdownview.css.styles.Github 6 | import cn.umafan.lib.android.databinding.ActivityUpdateLogBinding 7 | import cn.umafan.lib.android.model.MyBaseActivity 8 | 9 | class UpdateLogActivity : MyBaseActivity() { 10 | 11 | private lateinit var binding: ActivityUpdateLogBinding 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | 16 | binding = ActivityUpdateLogBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | 19 | setSupportActionBar(binding.toolbar) 20 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) 21 | 22 | binding.markdownView.addStyleSheet(Github()) 23 | .loadMarkdownFromUrl("https://umalib.github.io/UmaLibAndroid/update-log.md") 24 | } 25 | 26 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 27 | if (item.itemId == android.R.id.home) { 28 | onBackPressed() 29 | return true 30 | } 31 | return super.onOptionsItemSelected(item) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/favorites/FavoritesViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.favorites 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.viewModelScope 5 | import cn.umafan.lib.android.model.PageSelectorViewModel 6 | import cn.umafan.lib.android.model.db.ArtInfo 7 | import cn.umafan.lib.android.ui.home.model.ArticleInfoItem 8 | import cn.umafan.lib.android.ui.home.model.PageItem 9 | import com.angcyo.dsladapter.DslAdapter 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.launch 12 | 13 | class FavoritesViewModel : PageSelectorViewModel() { 14 | 15 | private val articleData = MutableStateFlow(listOf()) 16 | 17 | val currentPage = MutableLiveData(1) 18 | 19 | val articleDataAdapter = DslAdapter() 20 | 21 | val pageSelectorAdapter = DslAdapter() 22 | 23 | val pageData = MutableStateFlow(listOf()) 24 | 25 | val pageLen = MutableLiveData(1) 26 | 27 | init { 28 | viewModelScope.launch { 29 | articleData.collect { 30 | articleDataAdapter.changeDataItems { adapterItems -> 31 | adapterItems.clear() 32 | it.forEach { 33 | adapterItems.add(ArticleInfoItem(it)) 34 | } 35 | } 36 | } 37 | } 38 | viewModelScope.launch { 39 | pageData.collect { 40 | pageSelectorAdapter.changeDataItems { adapterItems -> 41 | adapterItems.clear() 42 | it.forEach { 43 | adapterItems.add(PageItem(it, this@FavoritesViewModel)) 44 | } 45 | } 46 | pageSelectorAdapter.notifyDataChanged() 47 | } 48 | } 49 | } 50 | 51 | fun loadArticles(data: MutableList) { 52 | viewModelScope.launch { 53 | articleData.emit(data) 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/history/HistoryFragment.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.history 2 | 3 | import android.annotation.SuppressLint 4 | import android.graphics.drawable.Drawable 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.fragment.app.Fragment 10 | import androidx.lifecycle.ViewModelProvider 11 | import cn.umafan.lib.android.R 12 | import cn.umafan.lib.android.databinding.FragmentHistroyBinding 13 | import cn.umafan.lib.android.model.DataBaseHandler 14 | import cn.umafan.lib.android.model.MyBaseActivity 15 | import cn.umafan.lib.android.model.db.ArtInfo 16 | import cn.umafan.lib.android.model.db.ArtInfoDao 17 | import cn.umafan.lib.android.model.db.DaoSession 18 | import cn.umafan.lib.android.ui.main.DatabaseCopyThread 19 | import cn.umafan.lib.android.ui.main.MainActivity 20 | import cn.umafan.lib.android.util.HistoryUtil 21 | import cn.umafan.lib.android.util.SettingUtil 22 | import com.liangguo.androidkit.app.ToastUtil 23 | 24 | @SuppressLint("InflateParams") 25 | class HistoryFragment : Fragment() { 26 | private var _binding: FragmentHistroyBinding? = null 27 | 28 | private val binding get() = _binding!! 29 | 30 | private lateinit var _historyViewModel: HistoryViewModel 31 | 32 | private val historyViewModel get() = _historyViewModel 33 | 34 | private var daoSession: DaoSession? = null 35 | 36 | @SuppressLint("SetTextI18n") 37 | override fun onCreateView( 38 | inflater: LayoutInflater, 39 | container: ViewGroup?, 40 | savedInstanceState: Bundle? 41 | ): View { 42 | _historyViewModel = 43 | ViewModelProvider(this)[HistoryViewModel::class.java] 44 | 45 | _binding = FragmentHistroyBinding.inflate(inflater, container, false) 46 | val root: View = binding.root 47 | 48 | initViews() 49 | 50 | loadArticles() 51 | 52 | return root 53 | } 54 | 55 | private fun initViews() { 56 | with(binding) { 57 | recyclerView.adapter = historyViewModel.articleDataAdapter 58 | layout.apply { 59 | val uri = SettingUtil.getImageBackground(SettingUtil.INDEX_BG) 60 | if (null != uri) background = Drawable.createFromPath(uri.path) 61 | } 62 | } 63 | } 64 | 65 | private fun loadArticles() { 66 | val idList = HistoryUtil.getHistory() 67 | if (idList.isEmpty()) { 68 | ToastUtil.info(getString(R.string.no_data)) 69 | } 70 | val handler = DataBaseHandler(activity as MyBaseActivity) { 71 | daoSession = it.obj as DaoSession 72 | if (null != daoSession) { 73 | val artInfoDao: ArtInfoDao = daoSession!!.artInfoDao 74 | val data = mutableListOf() 75 | idList.forEach { id -> 76 | val art = 77 | artInfoDao.queryBuilder().where(ArtInfoDao.Properties.Id.eq(id)).unique() 78 | if (null != art) data.add(art) 79 | } 80 | historyViewModel.loadArticles(data) 81 | } 82 | } 83 | (activity as MainActivity).shapeLoadingDialog?.show() 84 | DatabaseCopyThread.addHandler(handler) 85 | } 86 | 87 | override fun onResume() { 88 | super.onResume() 89 | loadArticles() 90 | } 91 | 92 | override fun onDestroyView() { 93 | super.onDestroyView() 94 | _binding = null 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/history/HistoryViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.history 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import cn.umafan.lib.android.model.PageSelectorViewModel 5 | import cn.umafan.lib.android.model.db.ArtInfo 6 | import cn.umafan.lib.android.ui.home.model.ArticleInfoItem 7 | import com.angcyo.dsladapter.DslAdapter 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.launch 10 | 11 | class HistoryViewModel : PageSelectorViewModel() { 12 | 13 | private val articleData = MutableStateFlow(listOf()) 14 | 15 | val articleDataAdapter = DslAdapter() 16 | 17 | init { 18 | viewModelScope.launch { 19 | articleData.collect { 20 | articleDataAdapter.changeDataItems { adapterItems -> 21 | adapterItems.clear() 22 | it.forEach { 23 | adapterItems.add(ArticleInfoItem(it)) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | fun loadArticles(data: MutableList) { 31 | viewModelScope.launch { 32 | articleData.emit(data) 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.home 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.viewModelScope 6 | import cn.umafan.lib.android.model.PageSelectorViewModel 7 | import cn.umafan.lib.android.model.db.ArtInfo 8 | import cn.umafan.lib.android.ui.home.model.ArticleInfoItem 9 | import cn.umafan.lib.android.ui.home.model.PageItem 10 | import com.angcyo.dsladapter.DslAdapter 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.launch 13 | 14 | class HomeViewModel : PageSelectorViewModel() { 15 | private val _text = MutableLiveData().apply { 16 | value = "This is home Fragment" 17 | } 18 | val text: LiveData = _text 19 | 20 | val pageLen = MutableLiveData(1) 21 | 22 | private val articleData = MutableStateFlow(listOf()) 23 | 24 | val currentPage = MutableLiveData(1) 25 | 26 | val articleDataAdapter = DslAdapter() 27 | 28 | val pageSelectorAdapter = DslAdapter() 29 | 30 | val pageData = MutableStateFlow(listOf()) 31 | 32 | init { 33 | viewModelScope.launch { 34 | articleData.collect { 35 | articleDataAdapter.changeDataItems { adapterItems -> 36 | adapterItems.clear() 37 | it.forEach { 38 | adapterItems.add(ArticleInfoItem(it)) 39 | } 40 | } 41 | } 42 | } 43 | viewModelScope.launch { 44 | pageData.collect { 45 | pageSelectorAdapter.changeDataItems { adapterItems -> 46 | adapterItems.clear() 47 | it.forEach { 48 | adapterItems.add(PageItem(it, this@HomeViewModel)) 49 | } 50 | } 51 | pageSelectorAdapter.notifyDataChanged() 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 异步加载文章 58 | */ 59 | fun loadArticles(list: List?) { 60 | viewModelScope.launch { 61 | var data = list 62 | if (null == data) { 63 | data = mutableListOf() 64 | } 65 | articleData.emit(data) 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/home/model/ArticleInfoItem.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.home.model 2 | 3 | import androidx.databinding.DataBindingUtil 4 | import cn.umafan.lib.android.R 5 | import cn.umafan.lib.android.databinding.ItemArticleCardBinding 6 | import cn.umafan.lib.android.model.db.ArtInfo 7 | import cn.umafan.lib.android.ui.reader.ReaderActivity 8 | import com.angcyo.dsladapter.DslAdapterItem 9 | import com.angcyo.dsladapter.DslViewHolder 10 | import com.google.android.material.snackbar.Snackbar 11 | import com.liangguo.androidkit.app.startNewActivity 12 | import java.text.SimpleDateFormat 13 | import java.util.* 14 | 15 | class ArticleInfoItem( 16 | private val articleInfo: ArtInfo 17 | ) : DslAdapterItem() { 18 | override var itemLayoutId = R.layout.item_article_card 19 | private val timeStampFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA) 20 | 21 | init { 22 | itemData = articleInfo 23 | thisAreContentsTheSame = { fromItem, newItem, _, _ -> 24 | fromItem?.itemData == newItem.itemData 25 | } 26 | thisAreItemsTheSame = { fromItem, newItem, _, _ -> 27 | fromItem?.itemData == newItem.itemData 28 | } 29 | } 30 | 31 | override fun onItemBind( 32 | itemHolder: DslViewHolder, 33 | itemPosition: Int, 34 | adapterItem: DslAdapterItem, 35 | payloads: List 36 | ) { 37 | super.onItemBind(itemHolder, itemPosition, adapterItem, payloads) 38 | itemHolder.view(R.id.item_article_card)?.let { 39 | DataBindingUtil.bind(it)?.apply { 40 | articleName.text = articleInfo.name 41 | articleNote.text = articleInfo.note 42 | if (articleInfo.translator.isNotEmpty()) { 43 | articleAuthor.text = articleInfo.author 44 | articleTranslator.text = String.format("译者:%s", articleInfo.translator) 45 | } else { 46 | articleAuthor.text = "" 47 | articleTranslator.text = articleInfo.author 48 | } 49 | 50 | articleUploadTime.text = articleInfo.uploadTime.let { date -> 51 | timeStampFormatter.format(date.toLong() * 1000) 52 | } 53 | articleTags.text = 54 | articleInfo.taggedList.map { tagged -> tagged.tag } 55 | .sortedWith { a, b -> 56 | if (a.type == b.type) 57 | a.name.compareTo(b.name) 58 | else 59 | b.type.compareTo(a.type) 60 | }.joinToString(" | ") { tag -> tag.name } 61 | itemArticleCardBox.setOnClickListener { 62 | ReaderActivity::class.startNewActivity { 63 | putExtra("id", articleInfo.id.toInt()) 64 | } 65 | } 66 | itemArticleCardBox.setOnLongClickListener { view -> 67 | Snackbar.make( 68 | view, 69 | "[ID${articleInfo.id}]${articleInfo.name}", 70 | Snackbar.LENGTH_SHORT 71 | ).show() 72 | false 73 | } 74 | invalidateAll() 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/home/model/PageItem.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.home.model 2 | 3 | import androidx.databinding.DataBindingUtil 4 | import cn.umafan.lib.android.R 5 | import cn.umafan.lib.android.databinding.ItemPageButtonBinding 6 | import cn.umafan.lib.android.model.PageSelectorViewModel 7 | import com.angcyo.dsladapter.DslAdapterItem 8 | import com.angcyo.dsladapter.DslViewHolder 9 | 10 | class PageItem( 11 | private val page: Int, 12 | private val mViewModel: PageSelectorViewModel 13 | ) : DslAdapterItem() { 14 | override var itemLayoutId = R.layout.item_page_button 15 | 16 | init { 17 | itemData = page 18 | thisAreContentsTheSame = { fromItem, newItem, _, _ -> 19 | fromItem?.itemData == newItem.itemData 20 | } 21 | thisAreItemsTheSame = { fromItem, newItem, _, _ -> 22 | fromItem?.itemData == newItem.itemData 23 | } 24 | } 25 | 26 | override fun onItemBind( 27 | itemHolder: DslViewHolder, 28 | itemPosition: Int, 29 | adapterItem: DslAdapterItem, 30 | payloads: List 31 | ) { 32 | super.onItemBind(itemHolder, itemPosition, adapterItem, payloads) 33 | itemHolder.view(R.id.item_page_button)?.let { 34 | DataBindingUtil.bind(it)?.apply { 35 | //先初始化使其不被选中,再通过数组储存的状态恢复选中 36 | itemPageButtonBox.isChecked = false 37 | itemPageButtonBox.isChecked = mViewModel.checkedList[page - 1] 38 | if (mViewModel.checkedList[page - 1]) { 39 | mViewModel.checkedButton.value = itemPageButtonBox 40 | } 41 | pageNumText.text = page.toString() 42 | itemPageButtonBox.setOnClickListener { 43 | itemPageButtonBox.isChecked = true 44 | with(mViewModel) { 45 | checkedButton.value?.isChecked = false 46 | checkedButton.value = itemPageButtonBox 47 | //清除数组再重新记录状态 48 | checkedList.replaceAll { false } 49 | checkedList[page - 1] = true 50 | 51 | selectedPage.value = page 52 | } 53 | 54 | } 55 | invalidateAll() 56 | } 57 | } 58 | } 59 | 60 | override fun onItemViewRecycled(itemHolder: DslViewHolder, itemPosition: Int) { 61 | itemHolder.view(R.id.item_page_button)?.let { 62 | DataBindingUtil.bind(it)?.apply { 63 | itemPageButtonBox.setOnCheckedChangeListener(null) 64 | } 65 | } 66 | super.onItemViewRecycled(itemHolder, itemPosition) 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/main/DatabaseCopyThread.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.main 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import android.util.Log 6 | import cn.umafan.lib.android.model.MyApplication 7 | import cn.umafan.lib.android.model.db.DaoMaster 8 | import cn.umafan.lib.android.model.db.DaoSession 9 | import cn.umafan.lib.android.util.ZipUtil 10 | import org.greenrobot.greendao.database.Database 11 | import java.io.File 12 | import java.io.FileOutputStream 13 | import java.io.InputStream 14 | import java.io.OutputStream 15 | 16 | 17 | class DatabaseCopyThread : Thread() { 18 | val context = MyApplication.context 19 | private lateinit var handler: Handler 20 | 21 | companion object { 22 | private var daoSession: DaoSession? = null 23 | private val queue = mutableListOf>() 24 | private val lock = Object() 25 | 26 | fun addHandler(_handler: Handler, fileName: String? = null) { 27 | queue.add(Pair(_handler, fileName)) 28 | synchronized(lock) { 29 | lock.notify() 30 | } 31 | } 32 | 33 | fun clearDb() { 34 | daoSession = null 35 | } 36 | 37 | fun reload() { 38 | MyApplication.context.getDatabasePath("main.db").delete() 39 | } 40 | } 41 | 42 | override fun run() { 43 | while (true) { 44 | while (queue.size > 0) { 45 | handler = queue.first().first 46 | val dbName = queue.first().second 47 | queue.removeFirst() 48 | if (null == daoSession) { 49 | copyDatabase(dbName) 50 | if (context.getDatabasePath("main.db").exists()) { 51 | val helper = LibOpenHelper(context, "main.db") 52 | val db: Database = helper.readableDb 53 | daoSession = DaoMaster(db).newSession() 54 | } 55 | } 56 | val message = handler.obtainMessage() 57 | message.what = MyApplication.DATABASE_LOADED 58 | message.obj = daoSession 59 | handler.sendMessage(message) 60 | } 61 | synchronized(lock) { 62 | lock.wait() 63 | } 64 | } 65 | } 66 | 67 | private fun copyDatabase(name: String? = null) { 68 | val dbFile: File = context.getDatabasePath("main.db") 69 | try { 70 | dbFile.parentFile?.mkdirs() 71 | if (null !== name) { 72 | if (!dbFile.exists()) { 73 | dbFile.createNewFile() 74 | } 75 | val output: String = dbFile.parent!! + "/" 76 | val input: String = context.getDatabasePath(name).path 77 | val unzippedFileName = ZipUtil.unzip(input, output) 78 | val unzippedFile = context.getDatabasePath(unzippedFileName) 79 | if (unzippedFile.exists()) { 80 | unzippedFile.renameTo(context.getDatabasePath("main.db")) 81 | // 删除压缩包 82 | context.getDatabasePath(name).delete() 83 | } else { 84 | return 85 | } 86 | Log.i(this.javaClass.simpleName, "unzip database done!") 87 | } else if (!dbFile.exists()) { 88 | val inputStream: InputStream = context.assets.open("db/main.db") 89 | val outputStream: OutputStream = FileOutputStream(dbFile) 90 | val buffer = ByteArray(5) 91 | var length: Int = inputStream.read(buffer) 92 | val total = inputStream.available() 93 | var count = 0 94 | 95 | while (length > 0) { 96 | if (count % 12000 == 0) { 97 | val progress = count * length / total.toDouble() * 100 98 | val message = handler.obtainMessage() 99 | message.what = MyApplication.DATABASE_LOADING 100 | message.obj = progress 101 | handler.sendMessage(message) 102 | } 103 | outputStream.write(buffer, 0, length) 104 | length = inputStream.read(buffer) 105 | count++ 106 | } 107 | outputStream.flush() 108 | outputStream.close() 109 | inputStream.close() 110 | Log.i(this.javaClass.simpleName, "copy database done!") 111 | } 112 | } catch (e: Exception) { 113 | e.printStackTrace() 114 | if (dbFile.exists()) { 115 | dbFile.delete() 116 | } 117 | } 118 | } 119 | 120 | class LibOpenHelper(val context: Context, val name: String) : 121 | DaoMaster.OpenHelper(context, name) { 122 | 123 | override fun onCreate(db: Database?) { 124 | super.onCreate(db) 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.main 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import cn.umafan.lib.android.model.SearchBean 6 | import cn.umafan.lib.android.model.db.Tag 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | 9 | class MainViewModel : ViewModel() { 10 | var searchParams = MutableLiveData(SearchBean()) 11 | var selectedTags = MutableStateFlow(mutableSetOf()) 12 | var selectedExceptTags = MutableStateFlow(mutableSetOf()) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/main/model/CreatorSuggestionAdapter.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.main.model 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.BaseAdapter 7 | import android.widget.Filter 8 | import android.widget.Filterable 9 | import cn.umafan.lib.android.R 10 | import cn.umafan.lib.android.model.MyApplication 11 | import cn.umafan.lib.android.util.PinyinUtil 12 | import com.google.android.material.textview.MaterialTextView 13 | import java.util.* 14 | 15 | 16 | /** 17 | * 用于搜索展示tag的适配器 18 | */ 19 | class CreatorSuggestionAdapter( 20 | private val tags: List 21 | ) : BaseAdapter(), Filterable { 22 | 23 | private var mArrayFilter: ArrayFilter? = null 24 | private var filterTags: List = tags 25 | 26 | override fun getFilter(): Filter { 27 | if (mArrayFilter == null) { 28 | mArrayFilter = ArrayFilter(tags, this) 29 | } 30 | return mArrayFilter!! 31 | } 32 | 33 | override fun getItem(p0: Int): Any { 34 | return filterTags[p0] 35 | } 36 | 37 | override fun getItemId(p0: Int): Long { 38 | return p0.toLong() 39 | } 40 | 41 | override fun getCount(): Int { 42 | return filterTags.size 43 | } 44 | 45 | override fun getView(positon: Int, convertView: View?, parent: ViewGroup?): View { 46 | val viewHolder: ViewHolder? 47 | var mConvertView: View? = convertView 48 | if (null == mConvertView) { 49 | viewHolder = ViewHolder() 50 | mConvertView = LayoutInflater.from(MyApplication.context) 51 | .inflate(R.layout.item_tag_suggestion, null) 52 | viewHolder.tagName = mConvertView.findViewById(R.id.item_tag_suggestion_name) 53 | mConvertView.tag = viewHolder 54 | } else { 55 | viewHolder = mConvertView.tag as ViewHolder 56 | } 57 | val tag = filterTags[positon] 58 | viewHolder.tagName?.text = tag 59 | 60 | return mConvertView!! 61 | } 62 | 63 | class ViewHolder( 64 | var tagName: MaterialTextView? = null 65 | ) 66 | 67 | private class ArrayFilter( 68 | private val tags: List, 69 | val adapter: CreatorSuggestionAdapter 70 | ) : Filter() { 71 | var mFilterTags: ArrayList? = null 72 | override fun performFiltering(constraint: CharSequence?): FilterResults { 73 | val results = FilterResults() 74 | if (mFilterTags == null) { 75 | mFilterTags = ArrayList(tags) 76 | } 77 | //如果没有过滤条件则不过滤 78 | if (constraint == null || constraint.isBlank()) { 79 | results.values = mFilterTags 80 | results.count = mFilterTags!!.size 81 | } else { 82 | val retList: MutableList = ArrayList() 83 | //过滤条件 84 | val str = constraint.toString() 85 | //循环变量数据源,如果有属性满足过滤条件,则添加到result中 86 | for (tag in mFilterTags!!) { 87 | if (PinyinUtil.getPinyin(tag, "").lowercase(Locale.getDefault()).contains( 88 | PinyinUtil.getPinyin(str, "").lowercase(Locale.getDefault()) 89 | ) 90 | ) { 91 | val chars: CharArray = str.toCharArray() 92 | var count = 0 93 | for (i in chars.indices) { 94 | // 判断是否为汉字字符 95 | if (chars[i].toString().matches(Regex("[\\u4E00-\\u9FA5]+"))) { 96 | count++ 97 | } 98 | } 99 | if (count > 0) { 100 | if (tag.contains(str)) { 101 | retList.add(tag) 102 | } 103 | } else retList.add(tag) 104 | } 105 | } 106 | results.values = retList 107 | results.count = retList.size 108 | } 109 | return results 110 | } 111 | 112 | //在这里返回过滤结果 113 | override fun publishResults( 114 | constraint: CharSequence?, 115 | results: FilterResults 116 | ) { 117 | adapter.filterTags = (results.values as List<*>).filterIsInstance() 118 | if (results.count > 0) { 119 | adapter.notifyDataSetChanged() 120 | } else { 121 | adapter.notifyDataSetInvalidated() 122 | } 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/main/model/TagSelectedItem.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.main.model 2 | 3 | import androidx.databinding.DataBindingUtil 4 | import androidx.lifecycle.viewModelScope 5 | import cn.umafan.lib.android.R 6 | import cn.umafan.lib.android.databinding.ItemSelectedTagBinding 7 | import cn.umafan.lib.android.model.db.Tag 8 | import cn.umafan.lib.android.ui.main.MainViewModel 9 | import com.angcyo.dsladapter.DslAdapterItem 10 | import com.angcyo.dsladapter.DslViewHolder 11 | import kotlinx.coroutines.launch 12 | 13 | 14 | class TagSelectedItem( 15 | val tag: Tag, 16 | private val mViewModel: MainViewModel, 17 | private val flag: Boolean 18 | ) : DslAdapterItem() { 19 | override var itemLayoutId = R.layout.item_selected_tag 20 | 21 | init { 22 | itemData = tag 23 | thisAreContentsTheSame = { fromItem, newItem, _, _ -> 24 | fromItem?.itemData == newItem.itemData 25 | } 26 | thisAreItemsTheSame = { fromItem, newItem, _, _ -> 27 | fromItem?.itemData == newItem.itemData 28 | } 29 | } 30 | 31 | override fun onItemBind( 32 | itemHolder: DslViewHolder, 33 | itemPosition: Int, 34 | adapterItem: DslAdapterItem, 35 | payloads: List 36 | ) { 37 | super.onItemBind(itemHolder, itemPosition, adapterItem, payloads) 38 | itemHolder.view(R.id.item_selected_tag)?.let { 39 | DataBindingUtil.bind(it)?.apply { 40 | itemSelectedTagName.text = tag.name 41 | itemSelectedTagCancel.setOnClickListener { 42 | with(mViewModel) { 43 | viewModelScope.launch { 44 | if (flag) { 45 | searchParams.value?.tags?.remove(tag) 46 | val tmp = mutableSetOf() 47 | searchParams.value?.tags?.forEach { tag -> 48 | tmp.add(tag) 49 | } 50 | selectedTags.emit(tmp) 51 | } else { 52 | searchParams.value?.exceptedTags?.remove(tag) 53 | val tmp = mutableSetOf() 54 | searchParams.value?.exceptedTags?.forEach { tag -> 55 | tmp.add(tag) 56 | } 57 | selectedExceptTags.emit(tmp) 58 | } 59 | } 60 | } 61 | } 62 | invalidateAll() 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/main/model/TagSuggestionAdapter.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.main.model 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.BaseAdapter 7 | import android.widget.Filter 8 | import android.widget.Filterable 9 | import cn.umafan.lib.android.R 10 | import cn.umafan.lib.android.model.MyApplication 11 | import cn.umafan.lib.android.model.db.Tag 12 | import cn.umafan.lib.android.util.PinyinUtil 13 | import com.google.android.material.textview.MaterialTextView 14 | import java.util.Locale 15 | 16 | 17 | /** 18 | * 用于搜索展示tag的适配器 19 | */ 20 | class TagSuggestionAdapter( 21 | private val tags: List 22 | ) : BaseAdapter(), Filterable { 23 | 24 | private var mArrayFilter: ArrayFilter? = null 25 | private var filterTags: List = tags 26 | 27 | override fun getFilter(): Filter { 28 | if (null == mArrayFilter) { 29 | mArrayFilter = ArrayFilter(tags, this) 30 | } 31 | return mArrayFilter!! 32 | } 33 | 34 | override fun getItem(p0: Int): Any { 35 | return filterTags[p0] 36 | } 37 | 38 | override fun getItemId(p0: Int): Long { 39 | return p0.toLong() 40 | } 41 | 42 | override fun getCount(): Int { 43 | return filterTags.size 44 | } 45 | 46 | override fun getView(positon: Int, convertView: View?, parent: ViewGroup?): View { 47 | val viewHolder: ViewHolder? 48 | var mConvertView: View? = convertView 49 | if (null == mConvertView) { 50 | viewHolder = ViewHolder() 51 | mConvertView = LayoutInflater.from(MyApplication.context) 52 | .inflate(R.layout.item_tag_suggestion, null) 53 | viewHolder.tagName = mConvertView.findViewById(R.id.item_tag_suggestion_name) 54 | mConvertView.tag = viewHolder 55 | } else { 56 | viewHolder = mConvertView.tag as ViewHolder 57 | } 58 | val tag = filterTags[positon] 59 | viewHolder.tagName?.text = tag.name 60 | 61 | return mConvertView!! 62 | } 63 | 64 | class ViewHolder( 65 | var tagName: MaterialTextView? = null 66 | ) 67 | 68 | private class ArrayFilter( 69 | private val tags: List, 70 | val adapter: TagSuggestionAdapter 71 | ) : Filter() { 72 | var mFilterTags: ArrayList? = null 73 | override fun performFiltering(constraint: CharSequence?): FilterResults { 74 | val results = FilterResults() 75 | if (mFilterTags == null) { 76 | mFilterTags = ArrayList(tags) 77 | } 78 | //如果没有过滤条件则不过滤 79 | if (constraint == null || constraint.isBlank()) { 80 | results.values = mFilterTags 81 | results.count = mFilterTags!!.size 82 | } else { 83 | val retList: MutableList = ArrayList() 84 | //过滤条件 85 | val str = constraint.toString() 86 | //循环变量数据源,如果有属性满足过滤条件,则添加到result中 87 | for (tag in mFilterTags!!) { 88 | val tagName = tag.name 89 | if (PinyinUtil.getPinyin(tagName, "").lowercase(Locale.getDefault()).contains( 90 | PinyinUtil.getPinyin(str, "").lowercase(Locale.getDefault()) 91 | ) 92 | ) { 93 | val chars: CharArray = str.toCharArray() 94 | var count = 0 95 | for (i in chars.indices) { 96 | // 判断是否为汉字字符 97 | if (chars[i].toString().matches(Regex("[\\u4E00-\\u9FA5]+"))) { 98 | count++ 99 | } 100 | } 101 | if (count > 0) { 102 | if (tagName.contains(str)) { 103 | retList.add(tag) 104 | } 105 | } else retList.add(tag) 106 | } 107 | } 108 | results.values = retList 109 | results.count = retList.size 110 | } 111 | return results 112 | } 113 | 114 | //在这里返回过滤结果 115 | override fun publishResults( 116 | constraint: CharSequence?, 117 | results: FilterResults 118 | ) { 119 | adapter.filterTags = (results.values as List<*>).filterIsInstance() 120 | if (results.count > 0) { 121 | adapter.notifyDataSetChanged() 122 | } else { 123 | adapter.notifyDataSetInvalidated() 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/reader/ReaderViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.reader 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import cn.umafan.lib.android.util.ReaderSettingUtil 6 | 7 | 8 | /** 9 | * @ClassName ReaderViewModel 10 | * @author Forever-DdB everddb@gmail.com 11 | * @Description 12 | * @createTime 2022年 08月07日 19:36 13 | **/ 14 | class ReaderViewModel : ViewModel() { 15 | val collected = MutableLiveData(true) 16 | val fontSize: MutableLiveData 17 | val segmentSpace: MutableLiveData 18 | 19 | init { 20 | val setting = ReaderSettingUtil.getSetting("default") 21 | fontSize = MutableLiveData(setting.getString("fontSize")) 22 | segmentSpace = MutableLiveData(setting.getString("segmentSpace")) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/reader/model/ReaderJSInterface.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.reader.model 2 | 3 | import android.text.TextUtils 4 | import android.util.Log 5 | import cn.umafan.lib.android.R 6 | import cn.umafan.lib.android.model.db.Article 7 | import cn.umafan.lib.android.util.ReaderSettingUtil 8 | import cn.umafan.lib.android.util.SettingUtil 9 | import com.itheima.view.BridgeWebView 10 | import org.json.JSONArray 11 | import org.json.JSONObject 12 | import java.text.SimpleDateFormat 13 | import java.util.* 14 | 15 | class ReaderJSInterface( 16 | private val webView: BridgeWebView, 17 | private val article: Article 18 | ) { 19 | 20 | companion object { 21 | val FORMATTER = SimpleDateFormat( 22 | "yyyy-MM-dd HH:mm", 23 | Locale.CHINA 24 | ) 25 | 26 | fun initiativeStyle(webView: BridgeWebView) { 27 | val callback = "renderAct" 28 | 29 | val json = JSONObject() 30 | json.put("setting", ReaderSettingUtil.getSetting("default")) 31 | 32 | if (TextUtils.isEmpty(callback)) return 33 | //调用js方法必须在主线程 34 | webView.post { webView.loadUrl("javascript:$callback($json)") } 35 | } 36 | } 37 | 38 | // 以此格式写方法 39 | fun getArticle(str: Array) { 40 | Log.d(this.javaClass.simpleName, str[0]) 41 | //解析js callback方法 42 | val mJson = JSONObject(str[0]) 43 | val callback = mJson.optString("callback") //解析js回调方法 44 | 45 | val json = JSONObject() 46 | json.put("name", article.name) 47 | json.put("source", article.source) 48 | json.put("note", article.note) 49 | json.put("translator", article.translator) 50 | json.put("author", article.author) 51 | 52 | var content = article.content 53 | val regex = Regex("

\\[[^]]+][^<]*

\$") 54 | val elTagRegex = Regex("<[^>]*>") 55 | val dict = mutableMapOf() 56 | var result: MatchResult? 57 | do { 58 | result = regex.find(content) 59 | if (null != result) { 60 | content = content.substring(0, result.range.first) 61 | val annotationArr = 62 | result.value.replace(elTagRegex, "").split(']') 63 | val value = annotationArr[annotationArr.size - 1] 64 | annotationArr 65 | .filterIndexed { i, _ -> i != annotationArr.size - 1 } 66 | .forEach { key -> 67 | dict[key.substring(1)] = value 68 | } 69 | } 70 | } while (null != result) 71 | if (dict.isNotEmpty()) { 72 | val emptyElRegex = Regex("

\\s*\\s*

") 73 | content = content.replace(emptyElRegex, "") 74 | dict.forEach { (key, value) -> 75 | content = content.replace( 76 | "[$key]", 77 | " [" + 79 | "$key]$value " 80 | ) 81 | } 82 | } 83 | json.put("content", content) 84 | 85 | 86 | val tagList = JSONArray(article.taggedList.sortedWith { a, b -> 87 | if (a.tag.type == b.tag.type) 88 | a.tag.name.compareTo(b.tag.name) 89 | else 90 | b.tag.type.compareTo(a.tag.type) 91 | }.map { tagged -> 92 | val tagJson = JSONObject() 93 | tagJson.put("name", tagged.tag.name) 94 | tagJson.put("type", tagged.tag.type) 95 | tagJson 96 | }) 97 | json.put("tags", tagList) 98 | json.put( 99 | "time", 100 | FORMATTER.format(article.uploadTime.toLong() * 1000) 101 | ) 102 | json.put("setting", ReaderSettingUtil.getSetting("default")) 103 | Log.d(this.javaClass.simpleName, SettingUtil.getTheme().toString()) 104 | json.getJSONObject("setting").put( 105 | "theme", when (SettingUtil.getTheme()) { 106 | R.style.Theme_UmaLibrary_NGA -> "nga" 107 | R.style.Theme_UmaLibrary_WHITE -> "white" 108 | R.style.Theme_UmaLibrary_TEAL -> "cyan" 109 | else -> "purple" 110 | } 111 | ) 112 | Log.d(this.javaClass.simpleName, json.toString()) 113 | 114 | invokeJavaScript(callback, json.toString()) 115 | } 116 | 117 | /** 118 | * 统一管理所有android调用js方法 119 | * 120 | * @param callback js回调方法名 121 | * @param json 传递json数据 122 | */ 123 | private fun invokeJavaScript(callback: String, json: String) { 124 | Log.d(this.javaClass.simpleName, "callbackName: $callback data: $json") 125 | if (TextUtils.isEmpty(callback)) return 126 | //调用js方法必须在主线程 127 | webView.post { webView.loadUrl("javascript:$callback($json)") } 128 | } 129 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/recommend/RecommendViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.recommend 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import cn.umafan.lib.android.ui.main.MainActivity 8 | import cn.umafan.lib.android.ui.recommend.model.RecInfo 9 | import cn.umafan.lib.android.ui.recommend.model.RecTabItem 10 | import com.angcyo.dsladapter.DslAdapter 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.launch 13 | 14 | class RecommendViewModel : ViewModel() { 15 | private val _text = MutableLiveData().apply { 16 | value = "This is home Fragment" 17 | } 18 | val text: LiveData = _text 19 | 20 | private val recData = MutableStateFlow(listOf()) 21 | 22 | val currentPage = MutableLiveData(1) 23 | 24 | val type = MutableLiveData(0) 25 | 26 | val recDataAdapter = DslAdapter() 27 | 28 | var notShowJumpButtonList = mutableListOf() 29 | 30 | var collapsedList = mutableListOf() 31 | 32 | lateinit var activity: MainActivity 33 | 34 | init { 35 | viewModelScope.launch { 36 | recData.collect { 37 | recDataAdapter.changeDataItems { adapterItems -> 38 | adapterItems.clear() 39 | it.forEach { 40 | adapterItems.add(RecTabItem(it, activity, this@RecommendViewModel)) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * 异步加载推荐数据 49 | */ 50 | fun loadRecs(list: List?) { 51 | viewModelScope.launch { 52 | var data = list 53 | if (null == data) { 54 | data = mutableListOf() 55 | } 56 | recData.emit(data) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/recommend/model/RecCommentItem.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.recommend.model 2 | 3 | import androidx.databinding.DataBindingUtil 4 | import cn.umafan.lib.android.R 5 | import cn.umafan.lib.android.databinding.ItemRecCommentBinding 6 | import cn.umafan.lib.android.model.db.Rec 7 | import com.angcyo.dsladapter.DslAdapterItem 8 | import com.angcyo.dsladapter.DslViewHolder 9 | 10 | 11 | /** 12 | * @ClassName RecCommentItem 13 | * @author ForeverDdB 835236331@qq.com 14 | * @Description 15 | * @createTime 2023年 06月12日 15:34 16 | **/ 17 | class RecCommentItem( 18 | private val recInfo: Rec 19 | ) : DslAdapterItem() { 20 | override var itemLayoutId = R.layout.item_rec_comment 21 | 22 | init { 23 | itemData = recInfo 24 | thisAreContentsTheSame = { fromItem, newItem, _, _ -> 25 | fromItem?.itemData == newItem.itemData 26 | } 27 | thisAreItemsTheSame = { fromItem, newItem, _, _ -> 28 | fromItem?.itemData == newItem.itemData 29 | } 30 | } 31 | 32 | override fun onItemBind( 33 | itemHolder: DslViewHolder, 34 | itemPosition: Int, 35 | adapterItem: DslAdapterItem, 36 | payloads: List 37 | ) { 38 | super.onItemBind(itemHolder, itemPosition, adapterItem, payloads) 39 | itemHolder.view(R.id.item_rec_comment)?.let { 40 | DataBindingUtil.bind(it)?.apply { 41 | recReason.text = recInfo.reason 42 | recAuthor.text = "—— ${recInfo.name}" 43 | 44 | invalidateAll() 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/recommend/model/RecInfo.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.recommend.model 2 | 3 | import cn.umafan.lib.android.model.db.Rec 4 | 5 | class RecInfo( 6 | var data: MutableList 7 | ): Rec() { 8 | lateinit var rec: Rec 9 | var classType: Int = 0 10 | // 传入Rec数据, 生成RecInfo 11 | companion object { 12 | fun fromRec(rec: Rec, data: MutableList, type: Int): RecInfo { 13 | val recInfo = RecInfo(data) 14 | 15 | recInfo.classType = type 16 | recInfo.id = rec.id 17 | recInfo.name = rec.name 18 | recInfo.type = rec.type 19 | recInfo.r = rec.r 20 | recInfo.reason = rec.reason 21 | recInfo.title = rec.title 22 | recInfo.refId = rec.refId 23 | recInfo.others = rec.others 24 | recInfo.rec = rec 25 | 26 | return recInfo 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/recommend/model/RecJumpItem.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.recommend.model 2 | 3 | import android.util.Log 4 | import androidx.databinding.DataBindingUtil 5 | import cn.umafan.lib.android.R 6 | import cn.umafan.lib.android.databinding.ItemJumpButtonBinding 7 | import cn.umafan.lib.android.model.SearchBean 8 | import cn.umafan.lib.android.ui.main.MainActivity 9 | import com.angcyo.dsladapter.DslAdapterItem 10 | import com.angcyo.dsladapter.DslViewHolder 11 | 12 | 13 | /** 14 | * @ClassName RecJumpItem 15 | * @author ForeverDdB 835236331@qq.com 16 | * @Description 17 | * @createTime 2023年 06月12日 23:36 18 | **/ 19 | class RecJumpItem( 20 | val name: String, 21 | val searchBean: SearchBean, 22 | val activity: MainActivity 23 | ) : DslAdapterItem() { 24 | override var itemLayoutId = R.layout.item_jump_button 25 | init { 26 | itemData = name 27 | thisAreContentsTheSame = { fromItem, newItem, _, _ -> 28 | fromItem?.itemData == newItem.itemData 29 | } 30 | thisAreItemsTheSame = { fromItem, newItem, _, _ -> 31 | fromItem?.itemData == newItem.itemData 32 | } 33 | } 34 | 35 | override fun onItemBind( 36 | itemHolder: DslViewHolder, 37 | itemPosition: Int, 38 | adapterItem: DslAdapterItem, 39 | payloads: List 40 | ) { 41 | super.onItemBind(itemHolder, itemPosition, adapterItem, payloads) 42 | itemHolder.view(R.id.item_jump_button)?.let { 43 | DataBindingUtil.bind(it)?.apply { 44 | jumpButton.text = name 45 | jumpButton.setOnClickListener { 46 | activity.searchByOption(searchBean) 47 | } 48 | invalidateAll() 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/setting/SettingViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.setting 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | 6 | class SettingViewModel : ViewModel() -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/thanks/ThanksFragment.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.thanks 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import cn.umafan.lib.android.databinding.FragmentThanksBinding 10 | import cn.umafan.lib.android.model.DataBaseHandler 11 | import cn.umafan.lib.android.model.MyApplication 12 | import cn.umafan.lib.android.model.MyBaseActivity 13 | import cn.umafan.lib.android.model.db.DaoSession 14 | import cn.umafan.lib.android.ui.main.DatabaseCopyThread 15 | import cn.umafan.lib.android.util.SettingUtil 16 | 17 | class ThanksFragment : Fragment() { 18 | private var _binding: FragmentThanksBinding? = null 19 | 20 | private var daoSession: DaoSession? = null 21 | 22 | private val binding get() = _binding!! 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, 26 | container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View { 29 | _binding = FragmentThanksBinding.inflate(inflater, container, false) 30 | 31 | with(binding) { 32 | appVersion.text = MyApplication.getVersion().name 33 | layout.apply { 34 | val uri = SettingUtil.getImageBackground(SettingUtil.INDEX_BG) 35 | if (null != uri) background = Drawable.createFromPath(uri.path) 36 | } 37 | } 38 | 39 | loadCreators() 40 | 41 | return binding.root 42 | } 43 | 44 | private fun loadCreators() { 45 | val handler = DataBaseHandler(activity as MyBaseActivity) { 46 | daoSession = it.obj as DaoSession 47 | if (null != daoSession) { 48 | val creatorDao = daoSession!!.creatorDao 49 | val creators = creatorDao.queryBuilder().build().listLazy().first().names 50 | binding.othersContent.text = 51 | creators.substring(2, creators.length - 2).replace("\",\"", " | ") 52 | } 53 | } 54 | DatabaseCopyThread.addHandler(handler) 55 | } 56 | 57 | override fun onDestroyView() { 58 | super.onDestroyView() 59 | _binding = null 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/ui/thanks/ThanksViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.ui.thanks 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | 7 | class ThanksViewModel : ViewModel() { 8 | private val _text = MutableLiveData().apply { 9 | value = "This is thanks Fragment" 10 | } 11 | val text: LiveData = _text 12 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/ContentUriUtil.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util; 2 | 3 | import android.content.ContentUris; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Environment; 8 | import android.provider.DocumentsContract; 9 | import android.provider.MediaStore; 10 | 11 | public class ContentUriUtil { 12 | public static String getAbsolutePath(Context context, Uri imageUri) { 13 | if (context == null || imageUri == null) 14 | return null; 15 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, imageUri)) { 16 | if (isExternalStorageDocument(imageUri)) { 17 | String docId = DocumentsContract.getDocumentId(imageUri); 18 | String[] split = docId.split(":"); 19 | String type = split[0]; 20 | if ("primary".equalsIgnoreCase(type)) { 21 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 22 | } 23 | } else if (isDownloadsDocument(imageUri)) { 24 | String id = DocumentsContract.getDocumentId(imageUri); 25 | Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)); 26 | return getDataColumn(context, contentUri, null, null); 27 | } else if (isMediaDocument(imageUri)) { 28 | String docId = DocumentsContract.getDocumentId(imageUri); 29 | String[] split = docId.split(":"); 30 | String type = split[0]; 31 | Uri contentUri = null; 32 | if ("image".equals(type)) { 33 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 34 | } else if ("video".equals(type)) { 35 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 36 | } else if ("audio".equals(type)) { 37 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 38 | } 39 | String selection = MediaStore.Images.Media._ID + "=?"; 40 | String[] selectionArgs = new String[]{split[1]}; 41 | return getDataColumn(context, contentUri, selection, selectionArgs); 42 | } 43 | } // MediaStore (and general) 44 | else if ("content".equalsIgnoreCase(imageUri.getScheme())) { 45 | // Return the remote address 46 | if (isGooglePhotosUri(imageUri)) 47 | return imageUri.getLastPathSegment(); 48 | return getDataColumn(context, imageUri, null, null); 49 | } 50 | // File 51 | else if ("file".equalsIgnoreCase(imageUri.getScheme())) { 52 | return imageUri.getPath(); 53 | } 54 | return null; 55 | } 56 | 57 | /** 58 | * 从本地设备数据库查询数据. 59 | * 60 | * @param context 上下文 61 | * @param uri 内容提供者的标识 62 | * @param selection 设置条件,相当于SQL语句中的where 63 | * @param selectionArgs 条件值 64 | * @return 查询结果 65 | */ 66 | public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { 67 | Cursor cursor = null; 68 | String column = MediaStore.Images.Media.DATA; 69 | String[] projection = {column}; //告诉Provider要返回的内容(列Column) 70 | try { 71 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); 72 | if (cursor != null && cursor.moveToFirst()) { 73 | int index = cursor.getColumnIndexOrThrow(column); 74 | return cursor.getString(index); 75 | } 76 | } finally { 77 | if (cursor != null) 78 | cursor.close(); 79 | } 80 | return null; 81 | } 82 | 83 | /** 84 | * @param uri The Uri to check. 85 | * @return Whether the Uri authority is ExternalStorageProvider. 86 | */ 87 | public static boolean isExternalStorageDocument(Uri uri) { 88 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 89 | } 90 | 91 | /** 92 | * @param uri The Uri to check. 93 | * @return Whether the Uri authority is DownloadsProvider. 94 | */ 95 | public static boolean isDownloadsDocument(Uri uri) { 96 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 97 | } 98 | 99 | /** 100 | * @param uri The Uri to check. 101 | * @return Whether the Uri authority is MediaProvider. 102 | */ 103 | public static boolean isMediaDocument(Uri uri) { 104 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 105 | } 106 | 107 | /** 108 | * @param uri The Uri to check. 109 | * @return Whether the Uri authority is Google Photos. 110 | */ 111 | public static boolean isGooglePhotosUri(Uri uri) { 112 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/HistoryUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util 2 | 3 | import android.content.SharedPreferences 4 | import cn.umafan.lib.android.model.MyApplication 5 | import cn.umafan.lib.android.model.db.Article 6 | import org.json.JSONArray 7 | import org.json.JSONObject 8 | 9 | object HistoryUtil { 10 | private const val fileName = "history" 11 | private const val maxLength = 20 12 | 13 | private var sharedPreferences: SharedPreferences = 14 | MyApplication.context.getSharedPreferences(fileName, 0) 15 | 16 | /** 17 | * 保存到历史记录 18 | */ 19 | fun saveHistory(article: Article): Boolean { 20 | return try { 21 | val editor = sharedPreferences.edit() 22 | val originData = JSONArray(sharedPreferences.getString(fileName, "[]")!!) 23 | for (i in 0 until originData.length()) { 24 | if (originData.getJSONObject(i).getInt("id") == article.id.toInt()) { 25 | val data = originData.getJSONObject(i) 26 | originData.remove(i) 27 | originData.put(data) 28 | editor.putString(fileName, originData.toString()) 29 | editor.apply() 30 | return true 31 | } 32 | } 33 | if (originData.length() >= maxLength) { 34 | originData.remove(0) 35 | } 36 | val data = JSONObject() 37 | data.put("id", article.id.toInt()) 38 | data.put("name", article.name) 39 | data.put("author", article.author) 40 | data.put("translator", article.translator) 41 | val tagList = JSONArray(article.taggedList.sortedWith { a, b -> 42 | if (a.tag.type == b.tag.type) 43 | a.tag.name.compareTo(b.tag.name) 44 | else 45 | b.tag.type.compareTo(a.tag.type) 46 | }.map { tagged -> tagged.tag.name }) 47 | data.put("tags", tagList) 48 | originData.put(data) 49 | editor.putString(fileName, originData.toString()) 50 | editor.apply() 51 | true 52 | } catch (e: Exception) { 53 | e.printStackTrace() 54 | false 55 | } 56 | } 57 | 58 | /** 59 | * 获取历史记录 60 | */ 61 | fun getHistory(): List { 62 | val originData = JSONArray(sharedPreferences.getString(fileName, "[]")) 63 | val data = mutableListOf() 64 | for (i in 0 until originData.length()) { 65 | data.add(originData.getJSONObject(originData.length() - i - 1).getInt("id")) 66 | } 67 | return data 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/PageSizeUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util 2 | 3 | import android.content.SharedPreferences 4 | import cn.umafan.lib.android.model.MyApplication 5 | 6 | 7 | object PageSizeUtil { 8 | private const val fileName = "page_size" 9 | 10 | private var sharedPreferences: SharedPreferences = 11 | MyApplication.context.getSharedPreferences(fileName, 0) 12 | 13 | fun getSize(): Int { 14 | return sharedPreferences.getInt( 15 | "size", 16 | 20 17 | ) 18 | } 19 | 20 | fun setSize(size: Int): Boolean { 21 | return try { 22 | sharedPreferences.edit().putInt("size", size).apply() 23 | true 24 | } catch (e: Exception) { 25 | e.printStackTrace() 26 | false 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/PinyinUtil.java: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util; 2 | 3 | 4 | import net.sourceforge.pinyin4j.PinyinHelper; 5 | import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; 6 | import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; 7 | import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; 8 | import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; 9 | import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; 10 | 11 | public class PinyinUtil { 12 | 13 | /** 14 | * 将汉字转换为全拼 15 | * 16 | * @param text 文本 17 | * @param separator 分隔符 18 | * @return {@link String} 19 | */ 20 | public static String getPinyin(String text, String separator) { 21 | char[] chars = text.toCharArray(); 22 | HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); 23 | // 设置大小写 24 | format.setCaseType(HanyuPinyinCaseType.LOWERCASE); 25 | // 设置声调表示方法 26 | format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); 27 | // 设置字母u表示方法 28 | format.setVCharType(HanyuPinyinVCharType.WITH_V); 29 | String[] s; 30 | String rs = ""; 31 | try { 32 | StringBuilder sb = new StringBuilder(); 33 | for (int i = 0; i < chars.length; i++) { 34 | // 判断是否为汉字字符 35 | if (String.valueOf(chars[i]).matches("[\\u4E00-\\u9FA5]+")) { 36 | s = PinyinHelper.toHanyuPinyinStringArray(chars[i], format); 37 | if (s != null) { 38 | sb.append(s[0]).append(separator); 39 | continue; 40 | } 41 | } 42 | sb.append(chars[i]); 43 | if ((i + 1 >= chars.length) || String.valueOf(chars[i + 1]).matches("[\\u4E00-\\u9FA5]+")) { 44 | sb.append(separator); 45 | } 46 | } 47 | rs = sb.substring(0, sb.length()); 48 | } catch (BadHanyuPinyinOutputFormatCombination e) { 49 | e.printStackTrace(); 50 | } 51 | return rs; 52 | } 53 | 54 | /** 55 | * 获取汉字首字母 56 | * 57 | * @param text 文本 58 | * @return {@link String} 59 | */ 60 | public static String getPinyinInitials(String text) { 61 | StringBuilder sb = new StringBuilder(); 62 | for (int i = 0; i < text.length(); i++) { 63 | char ch = text.charAt(i); 64 | String[] s = PinyinHelper.toHanyuPinyinStringArray(ch); 65 | if (s != null) { 66 | sb.append(s[0].charAt(0)); 67 | } else { 68 | sb.append(ch); 69 | } 70 | } 71 | return sb.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/ReaderSettingUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util 2 | 3 | import android.content.SharedPreferences 4 | import cn.umafan.lib.android.model.MyApplication 5 | import org.json.JSONObject 6 | 7 | 8 | object ReaderSettingUtil { 9 | 10 | private const val fileName = "reader_setting" 11 | 12 | private var sharedPreferences: SharedPreferences = 13 | MyApplication.context.getSharedPreferences(fileName, 0) 14 | 15 | fun getSetting(theme: String): JSONObject { 16 | return JSONObject( 17 | sharedPreferences.getString( 18 | theme, 19 | "{\"fontSize\": \"normal\", \"segmentSpace\": \"wider\"}" 20 | )!! 21 | ) 22 | } 23 | 24 | fun setSetting(theme: String, fontSize: String, segmentSpace: String): Boolean { 25 | val map = mutableMapOf(Pair("fontSize", fontSize), Pair("segmentSpace", segmentSpace)) 26 | val json = (map as Map<*, *>?)?.let { JSONObject(it) } 27 | return try { 28 | sharedPreferences.edit().putString(theme, json.toString()).apply() 29 | true 30 | } catch (e: Exception) { 31 | e.printStackTrace() 32 | false 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/ZipUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util 2 | 3 | import java.io.BufferedInputStream 4 | import java.io.BufferedOutputStream 5 | import java.io.File 6 | import java.io.FileOutputStream 7 | import java.util.* 8 | import java.util.zip.ZipEntry 9 | import java.util.zip.ZipFile 10 | 11 | 12 | /** 13 | * @author HY 14 | */ 15 | object ZipUtil { 16 | private const val BUFFER = 1024 17 | 18 | fun unzip(filePath: String?, zipDir: String): String { 19 | var name = "" 20 | try { 21 | var bufferedOutputStream: BufferedOutputStream? 22 | var bufferedInputStream: BufferedInputStream? 23 | var entry: ZipEntry 24 | val zipFile = ZipFile(filePath) 25 | val dir: Enumeration<*> = zipFile.entries() 26 | while (dir.hasMoreElements()) { 27 | entry = dir.nextElement() as ZipEntry 28 | if (entry.isDirectory) { 29 | name = entry.name 30 | name = name.substring(0, name.length - 1) 31 | val fileObject = File(zipDir + name) 32 | fileObject.mkdir() 33 | } 34 | name = entry.name 35 | } 36 | val e: Enumeration<*> = zipFile.entries() 37 | while (e.hasMoreElements()) { 38 | entry = e.nextElement() as ZipEntry 39 | if (entry.isDirectory) { 40 | continue 41 | } else { 42 | bufferedInputStream = BufferedInputStream(zipFile.getInputStream(entry)) 43 | var count: Int 44 | val dataByte = ByteArray(BUFFER) 45 | val fos = FileOutputStream(zipDir + entry.name) 46 | bufferedOutputStream = BufferedOutputStream(fos, BUFFER) 47 | while ( 48 | bufferedInputStream.read(dataByte, 0, BUFFER) 49 | .also { count = it } != -1 50 | ) { 51 | bufferedOutputStream.write(dataByte, 0, count) 52 | } 53 | bufferedOutputStream.flush() 54 | bufferedOutputStream.close() 55 | bufferedInputStream.close() 56 | } 57 | } 58 | } catch (e: Exception) { 59 | e.printStackTrace() 60 | } 61 | return name 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/network/ServiceCreator.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util.network 2 | 3 | import android.annotation.SuppressLint 4 | import okhttp3.OkHttpClient 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | import java.net.Proxy 8 | import java.security.SecureRandom 9 | import java.security.cert.* 10 | import javax.net.ssl.SSLContext 11 | import javax.net.ssl.SSLSocketFactory 12 | import javax.net.ssl.TrustManager 13 | import javax.net.ssl.X509TrustManager 14 | 15 | 16 | object ServiceCreator { 17 | 18 | private fun createSSLSocketFactory(): SSLSocketFactory? { 19 | var ssfFactory: SSLSocketFactory? = null 20 | try { 21 | val mMyTrustManager = MyTrustManager() 22 | val sc = SSLContext.getInstance("TLS") 23 | sc.init(null, arrayOf(mMyTrustManager), SecureRandom()) 24 | ssfFactory = sc.socketFactory 25 | } catch (ignored: Exception) { 26 | ignored.printStackTrace() 27 | } 28 | return ssfFactory 29 | } 30 | 31 | //实现X509TrustManager接口 32 | @SuppressLint("CustomX509TrustManager") 33 | class MyTrustManager : X509TrustManager { 34 | @SuppressLint("TrustAllX509TrustManager") 35 | @Throws(CertificateException::class) 36 | override fun checkClientTrusted(chain: Array?, authType: String?) {} 37 | @SuppressLint("TrustAllX509TrustManager") 38 | @Throws(CertificateException::class) 39 | override fun checkServerTrusted(chain: Array?, authType: String?) {} 40 | 41 | override fun getAcceptedIssuers(): Array { 42 | return arrayOf() 43 | } 44 | } 45 | 46 | 47 | private const val BASE_URL = "https://umalib.gitgud.site/" 48 | 49 | private val okHttpClient = OkHttpClient.Builder() 50 | .proxy(Proxy.NO_PROXY) 51 | .sslSocketFactory(createSSLSocketFactory()!!, MyTrustManager()) 52 | .build() 53 | 54 | private val retrofit: Retrofit = Retrofit.Builder() 55 | .baseUrl(BASE_URL) 56 | .client(okHttpClient) 57 | .addConverterFactory(GsonConverterFactory.create()) 58 | .build() 59 | 60 | fun create(serviceClass: Class): T = retrofit.create(serviceClass) 61 | 62 | inline fun create(): T = create(T::class.java) 63 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/network/UpdateUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util.network 2 | 3 | import cn.umafan.lib.android.util.network.service.UpdateService 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | 7 | typealias MyHeaderMap = MutableMap 8 | 9 | fun getHeaderMap(): MyHeaderMap = mutableMapOf( 10 | Pair("Content-type", "application/json") 11 | ) 12 | 13 | object UpdateUtil { 14 | private val updateService by lazy { 15 | ServiceCreator.create() 16 | } 17 | 18 | suspend fun getUpdate() = catchError { 19 | updateService.getUpdate(getHeaderMap()) 20 | } 21 | 22 | /** 23 | * 所有的网络请求都统一经过这个函数 24 | */ 25 | private suspend fun catchError(block: suspend () -> T): T? { 26 | return try { 27 | val result = withContext(Dispatchers.IO) { block() } 28 | result 29 | } catch (e: Exception) { 30 | e.printStackTrace() 31 | if (e is retrofit2.HttpException) { 32 | null 33 | } else { 34 | null 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/network/model/UpdateBean.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util.network.model 2 | 3 | data class UpdateBean( 4 | val button: UpdateButtonBean, 5 | val currentVersionName: String = "", 6 | val currentVersion: Int = 0, 7 | val info: UpdateInfoBean, 8 | var show: Int = 0, 9 | var initiative: Boolean = false, 10 | var currentDb: Int = 0, 11 | var downloadUrl: String = "" 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/network/model/UpdateButtonBean.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util.network.model 2 | 3 | data class UpdateButtonBean( 4 | val text: String = "", 5 | val url: String = "" 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/network/model/UpdateInfoBean.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util.network.model 2 | 3 | data class UpdateInfoBean( 4 | val title: String = "", 5 | val message: String = "" 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/cn/umafan/lib/android/util/network/service/UpdateService.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android.util.network.service 2 | 3 | import cn.umafan.lib.android.util.network.MyHeaderMap 4 | import cn.umafan.lib.android.util.network.model.UpdateBean 5 | import retrofit2.http.GET 6 | import retrofit2.http.HeaderMap 7 | 8 | 9 | interface UpdateService { 10 | 11 | @GET("/UmaLibAndroid/update-info.json") 12 | suspend fun getUpdate( 13 | @HeaderMap headerMap: MyHeaderMap 14 | ): UpdateBean 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_github.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/app_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_database_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/app_database_intro.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_func_intro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/app_func_intro.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_tag_intro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/app_tag_intro.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_view_intro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/app_view_intro.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_arrow_right_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_help_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_menu_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_menu_open_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_volunteer_activism_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_article_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_cancel_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_chat_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_cloud_download_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_cloud_upload_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_color_lens_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_filter_alt_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_history_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_insert_photo_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_navigate_before_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_navigate_next_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_recommend_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_refresh_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_search_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_star_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_star_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_system_update_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lightbulb_24px.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/index_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/index_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/kitasan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/kitasan.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/normal_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/normal_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tokaiteio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/drawable/tokaiteio.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_intro.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_reader.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 24 | 25 | 29 | 30 | 35 | 36 | 47 | 48 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_update_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 24 | 25 | 26 | 27 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 25 | 26 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 53 | 54 | 57 | 58 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_loading_database.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 29 | 30 | 41 | 42 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_page_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 23 | 24 | 31 | 32 | 33 | 34 | 47 | 48 | 53 | 54 | 55 | 56 | 57 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_reader_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 24 | 25 | 32 | 33 | 34 | 35 | 43 | 44 | 57 | 58 | 66 | 67 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_collections.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_favorites.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 30 | 31 | 43 | 44 | 56 | 57 | 69 | 70 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_histroy.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 30 | 31 | 43 | 44 | 56 | 57 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_recommend.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 22 | 23 | 27 | 28 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_jump_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_page_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 31 | 32 | 36 | 37 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_rec_comment.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 32 | 33 | 36 | 37 | 51 | 52 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_selected_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 30 | 31 | 35 | 36 | 46 | 47 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_tag_suggestion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 15 | 19 | 23 | 27 | 28 | 29 | 31 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 15 | 21 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/navigation/mobile_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | 26 | 31 | 32 | 37 | 38 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 48dp 3 | 48dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-w1240dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 200dp 3 | -------------------------------------------------------------------------------- /app/src/main/res/values-w600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 48dp 3 | 48dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #1FA3A2 8 | #FF018786 9 | #FF000000 10 | #FFFFFFFF 11 | #AAAAAA 12 | #DDDDDD 13 | #f3e3fc 14 | #342c3c 15 | #03A9F4 16 | #cc7ede 17 | 18 | #d6cab5 19 | #1c1c24 20 | #3b2620 21 | #bfb3ac 22 | #b1b4b0 23 | #be914d 24 | #faf9e1 25 | #332807 26 | 27 | #fbfbfb 28 | #DDDDDD 29 | #BBBBBB 30 | #cae2ec 31 | #98b2d5 32 | #5d98e8 33 | #999999 34 | #AAAAAA 35 | 36 | #cbeaac 37 | #57a86c 38 | #608d8a 39 | #cf8045 40 | #e69048 41 | #dcb26e 42 | #C4F6DA 43 | #c17949 44 | #206C32 45 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 180dp 3 | 16dp 4 | 16dp 5 | 6 | 16dp 7 | 16dp 8 | 8dp 9 | 176dp 10 | 35dp 11 | 150dp 12 | 150dp 13 | 120dp 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/cn/umafan/lib/android/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.umafan.lib.android 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | maven { url 'https://jitpack.io' } 6 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } 7 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } 8 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' } 9 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' } 10 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 11 | maven { url "https://raw.githubusercontent.com/Pgyer/mvn_repo_pgyer/master" } 12 | maven { url "https://maven.admobile.top/repository/maven-releases/" } 13 | } 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:7.4.1' 16 | classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' 17 | } 18 | } 19 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 20 | plugins { 21 | id 'com.android.application' version '7.4.1' apply false 22 | id 'com.android.library' version '7.4.1' apply false 23 | id 'org.jetbrains.kotlin.android' version '1.7.0' apply false 24 | } 25 | 26 | allprojects { 27 | repositories { 28 | google() 29 | mavenCentral() 30 | maven { url 'https://jitpack.io' } 31 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } 32 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } 33 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' } 34 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' } 35 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 36 | maven { url "https://raw.githubusercontent.com/Pgyer/mvn_repo_pgyer/master" } 37 | maven { url "https://maven.admobile.top/repository/maven-releases/" } 38 | } 39 | } 40 | 41 | task clean(type: Delete) { 42 | delete rootProject.buildDir 43 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.enableJetifier=true 25 | android.injected.testOnly=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jul 29 02:21:05 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /preference/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /preference/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 31 8 | 9 | defaultConfig { 10 | minSdkVersion 21 11 | targetSdkVersion 31 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | kotlinOptions { 28 | jvmTarget = '1.8' 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation 'com.github.ldh-star:EasyingContext:1.0.4' 34 | implementation 'androidx.preference:preference:1.2.0-rc01' 35 | implementation 'com.google.android.material:material:1.6.0-alpha02' 36 | } -------------------------------------------------------------------------------- /preference/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umalib/UmaLibAndroid/f3ee03901c0e20469fdd4e2e7ca696b7c0e71f7f/preference/consumer-rules.pro -------------------------------------------------------------------------------- /preference/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /preference/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /preference/src/main/java/com/liangguo/preference/views/CommonSettingView.kt: -------------------------------------------------------------------------------- 1 | package com.liangguo.preference.views 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.util.AttributeSet 6 | import android.view.LayoutInflater 7 | import android.widget.FrameLayout 8 | import androidx.appcompat.widget.AppCompatImageView 9 | import androidx.core.view.isVisible 10 | import com.google.android.material.textview.MaterialTextView 11 | import com.liangguo.preference.R 12 | 13 | 14 | /** 15 | * @author ldh 16 | * 时间: 2022/3/17 14:16 17 | * 18 | */ 19 | @SuppressLint("CustomViewStyleable") 20 | class CommonSettingView @JvmOverloads constructor( 21 | context: Context, 22 | attrs: AttributeSet? = null, 23 | defStyleAttr: Int = -1 24 | ) : FrameLayout(context, attrs, defStyleAttr) { 25 | 26 | val titleTextView by lazy { 27 | findViewById(android.R.id.title) 28 | } 29 | 30 | val subtitleTextView by lazy { 31 | findViewById(android.R.id.summary) 32 | } 33 | 34 | val endTextView by lazy { 35 | findViewById(R.id.textView_end) 36 | } 37 | 38 | val iconView by lazy { 39 | findViewById(android.R.id.icon) 40 | } 41 | 42 | val endIconView by lazy { 43 | findViewById(R.id.imageView_right_icon) 44 | } 45 | 46 | init { 47 | LayoutInflater.from(context).inflate(R.layout.item_common_preference, this, true) 48 | context.obtainStyledAttributes(attrs, R.styleable.CommonSettingsView, defStyleAttr, 0) 49 | .apply { 50 | titleTextView.text = getText(R.styleable.CommonSettingsView_title) 51 | subtitleTextView.text = getText(R.styleable.CommonSettingsView_subtitle) 52 | endTextView.text = getText(R.styleable.CommonSettingsView_end_text) 53 | endIconView.isVisible = getBoolean(R.styleable.CommonSettingsView_showEndIcon, true) 54 | iconView.setImageDrawable(getDrawable(R.styleable.CommonSettingsView_icon)) 55 | }.recycle() 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /preference/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /preference/src/main/res/drawable/switch_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /preference/src/main/res/drawable/switch_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /preference/src/main/res/layout/item_common_preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 24 | 25 | 39 | 40 | 55 | 56 | 62 | 63 | 71 | 72 | 86 | 87 | 88 | 89 | 90 | 100 | 101 | -------------------------------------------------------------------------------- /preference/src/main/res/values/attrs_baseline_textview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /preference/src/main/res/values/attrs_common_setting_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /preference/src/main/res/values/attrs_pref_common.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /preference/src/main/res/values/attrs_pref_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /preference/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56dp 4 | -------------------------------------------------------------------------------- /preference/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 确定 4 | 取消 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven { url 'https://jitpack.io' } 7 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } 8 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } 9 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' } 10 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' } 11 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 12 | maven { url "https://raw.githubusercontent.com/Pgyer/mvn_repo_pgyer/master" } 13 | maven { url "https://maven.admobile.top/repository/maven-releases/" } 14 | } 15 | } 16 | 17 | //dependencyResolutionManagement { 18 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 19 | // repositories { 20 | // google() 21 | // mavenCentral() 22 | // maven { url 'https://jitpack.io' } 23 | // maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } 24 | // maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } 25 | // maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' } 26 | // maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' } 27 | // maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 28 | // maven { url "https://raw.githubusercontent.com/Pgyer/mvn_repo_pgyer/master" } 29 | // maven { url "https://maven.admobile.top/repository/maven-releases/" } 30 | // } 31 | //} 32 | rootProject.name = "UmaLibAndroid" 33 | include ':app' 34 | include ':preference' 35 | --------------------------------------------------------------------------------